initial commit
This commit is contained in:
commit
3fffe798cd
8
.gitignore
vendored
Executable file
8
.gitignore
vendored
Executable file
@ -0,0 +1,8 @@
|
|||||||
|
bin/
|
||||||
|
target/
|
||||||
|
.settings/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
hs_err*.log
|
||||||
|
application.properties
|
||||||
|
usernames.txt
|
183
pom.xml
Normal file
183
pom.xml
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<groupId>de.bstly.board</groupId>
|
||||||
|
<artifactId>bstlboard</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<java.version>11</java.version>
|
||||||
|
<revision>0.2.2-SNAPSHOT</revision>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>2.5.5</version>
|
||||||
|
<relativePath />
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-oauth2-client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.session</groupId>
|
||||||
|
<artifactId>spring-session-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Query DSL -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.querydsl</groupId>
|
||||||
|
<artifactId>querydsl-apt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.querydsl</groupId>
|
||||||
|
<artifactId>querydsl-jpa</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Utils -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-validator</groupId>
|
||||||
|
<artifactId>commons-validator</artifactId>
|
||||||
|
<version>1.7</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.code.gson</groupId>
|
||||||
|
<artifactId>gson</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.googlecode.owasp-java-html-sanitizer</groupId>
|
||||||
|
<artifactId>owasp-java-html-sanitizer</artifactId>
|
||||||
|
<version>20200713.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
<version>1.68</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<!-- Database -->
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>db-inmemory</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.hsqldb</groupId>
|
||||||
|
<artifactId>hsqldb</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-mariadb</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mariadb.jdbc</groupId>
|
||||||
|
<artifactId>mariadb-java-client</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-mysql</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>mysql</groupId>
|
||||||
|
<artifactId>mysql-connector-java</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>db-postgresql</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<mainClass>de.bstly.board.Application</mainClass>
|
||||||
|
<finalName>bstlboard</finalName>
|
||||||
|
<executable>true</executable>
|
||||||
|
<layout>ZIP</layout>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>build-info</id>
|
||||||
|
<goals>
|
||||||
|
<goal>build-info</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.mysema.maven</groupId>
|
||||||
|
<artifactId>apt-maven-plugin</artifactId>
|
||||||
|
<version>1.1.3</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>process</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<outputDirectory>target/generated-sources/java</outputDirectory>
|
||||||
|
<processor>com.querydsl.apt.jpa.JPAAnnotationProcessor
|
||||||
|
</processor>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
25
src/main/java/de/bstly/board/Application.java
Executable file
25
src/main/java/de/bstly/board/Application.java
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author spring-cachet-monitoring@champonthis.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
public class Application extends SpringBootServletInitializer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param args
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/main/java/de/bstly/board/JPAConfig.java
Normal file
31
src/main/java/de/bstly/board/JPAConfig.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board;
|
||||||
|
|
||||||
|
import javax.persistence.EntityManager;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@EnableJpaAuditing()
|
||||||
|
public class JPAConfig {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntityManager em;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JPAQueryFactory jpaQueryFactory() {
|
||||||
|
return new JPAQueryFactory(em);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
39
src/main/java/de/bstly/board/MessageSourceConfig.java
Normal file
39
src/main/java/de/bstly/board/MessageSourceConfig.java
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board;
|
||||||
|
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.MessageSource;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class MessageSourceConfig {
|
||||||
|
|
||||||
|
@Value("${bstly.board.title:bstlboard}")
|
||||||
|
private String title;
|
||||||
|
@Value("${bstly.board.url:http://localhost:8080}")
|
||||||
|
private String baseUrl;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public MessageSource messageSource() {
|
||||||
|
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
|
||||||
|
|
||||||
|
messageSource.setBasename("classpath:messages");
|
||||||
|
messageSource.setDefaultEncoding("UTF-8");
|
||||||
|
Properties commonMessages = new Properties();
|
||||||
|
commonMessages.put("title", title);
|
||||||
|
commonMessages.put("baseUrl", baseUrl);
|
||||||
|
|
||||||
|
messageSource.setCommonMessages(commonMessages);
|
||||||
|
return messageSource;
|
||||||
|
}
|
||||||
|
}
|
185
src/main/java/de/bstly/board/businesslogic/CommentManager.java
Normal file
185
src/main/java/de/bstly/board/businesslogic/CommentManager.java
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.businesslogic;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.domain.Sort.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Comment;
|
||||||
|
import de.bstly.board.model.QComment;
|
||||||
|
import de.bstly.board.model.QVote;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
import de.bstly.board.repository.CommentRepository;
|
||||||
|
import de.bstly.board.repository.VoteRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class CommentManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommentRepository commentRepository;
|
||||||
|
@Autowired
|
||||||
|
private VoteRepository voteRepository;
|
||||||
|
@Autowired
|
||||||
|
private VoteManager voteManager;
|
||||||
|
private QComment qComment = QComment.comment;
|
||||||
|
private QVote qVote = QVote.vote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param parent
|
||||||
|
* @param target
|
||||||
|
* @param page
|
||||||
|
* @param size
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
|
||||||
|
public Page<Comment> fetchByRanking(Long target, Long parent, Instant date, double gravity,
|
||||||
|
int page, int size) {
|
||||||
|
if (parent == null) {
|
||||||
|
return commentRepository.findAllByRankingAndParent(target, date, gravity,
|
||||||
|
PageRequest.of(page, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentRepository.findAllByRankingAndParent(target, parent, date, gravity,
|
||||||
|
PageRequest.of(page, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param page
|
||||||
|
* @param size
|
||||||
|
* @param order
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Page<Comment> fetchByDate(Long target, Long parent, Instant date, int page, int size) {
|
||||||
|
if (parent == null) {
|
||||||
|
return commentRepository.findAll(
|
||||||
|
qComment.target.eq(target).and(qComment.parent.isNull())
|
||||||
|
.and(qComment.created.before(date)),
|
||||||
|
PageRequest.of(page, size, Sort.by(Order.asc("created"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentRepository.findAll(
|
||||||
|
qComment.target.eq(target).and(qComment.parent.eq(parent))
|
||||||
|
.and(qComment.created.before(date)),
|
||||||
|
PageRequest.of(page, size, Sort.by(Order.asc("created"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param parent
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Long count(Long target, Long parent) {
|
||||||
|
if (parent == null) {
|
||||||
|
return count(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
return commentRepository.count(qComment.target.eq(target).and(qComment.parent.eq(parent)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Long count(Long target) {
|
||||||
|
return commentRepository.count(qComment.target.eq(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param comment
|
||||||
|
*/
|
||||||
|
public void applyMetadata(String username, Comment comment) {
|
||||||
|
if (!comment.getMetadata().containsKey("comments")) {
|
||||||
|
comment.getMetadata().put("comments", count(comment.getTarget(), comment.getId()));
|
||||||
|
}
|
||||||
|
if (!comment.getMetadata().containsKey("points")) {
|
||||||
|
comment.getMetadata().put("points",
|
||||||
|
voteManager.getPoints(comment.getId(), Types.entry));
|
||||||
|
}
|
||||||
|
if (!comment.getMetadata().containsKey("vote")) {
|
||||||
|
comment.getMetadata().put("vote",
|
||||||
|
!voteRepository.exists(qVote.target.eq(comment.getId())
|
||||||
|
.and(qVote.targetType.eq(Types.comment)).and(qVote.type.eq(VoteType.up))
|
||||||
|
.and(qVote.author.eq(username))));
|
||||||
|
}
|
||||||
|
if (!comment.getMetadata().containsKey("unvote")) {
|
||||||
|
comment.getMetadata().put("unvote",
|
||||||
|
voteRepository.exists(qVote.target.eq(comment.getId())
|
||||||
|
.and(qVote.targetType.eq(Types.comment))
|
||||||
|
.and(qVote.type.eq(VoteType.up).and(qVote.author.eq(username)))));
|
||||||
|
}
|
||||||
|
if (!comment.getMetadata().containsKey("downvote")) {
|
||||||
|
comment.getMetadata()
|
||||||
|
.put("downvote",
|
||||||
|
!voteRepository.exists(qVote.target.eq(comment.getId())
|
||||||
|
.and(qVote.targetType.eq(Types.comment))
|
||||||
|
.and(qVote.author.eq(username))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param entries
|
||||||
|
*/
|
||||||
|
public void applyMetadata(String username, List<Comment> entries) {
|
||||||
|
for (Comment comment : entries) {
|
||||||
|
applyMetadata(username, comment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean exists(Long id) {
|
||||||
|
return commentRepository.existsById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Comment get(Long id) {
|
||||||
|
return commentRepository.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param comment
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Comment save(Comment comment) {
|
||||||
|
return commentRepository.save(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param commentId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getPoints(Long commentId) {
|
||||||
|
long upvotes = voteRepository.count(qVote.targetType.eq(Types.comment)
|
||||||
|
.and(qVote.type.eq(VoteType.up)).and(qVote.target.eq(commentId)));
|
||||||
|
long downvotes = voteRepository.count(qVote.targetType.eq(Types.comment)
|
||||||
|
.and(qVote.type.eq(VoteType.down)).and(qVote.target.eq(commentId)));
|
||||||
|
return upvotes - downvotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
158
src/main/java/de/bstly/board/businesslogic/EntryManager.java
Normal file
158
src/main/java/de/bstly/board/businesslogic/EntryManager.java
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.businesslogic;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageRequest;
|
||||||
|
import org.springframework.data.domain.Sort;
|
||||||
|
import org.springframework.data.domain.Sort.Order;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Entry;
|
||||||
|
import de.bstly.board.model.QEntry;
|
||||||
|
import de.bstly.board.model.QVote;
|
||||||
|
import de.bstly.board.model.RankedEntry;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
import de.bstly.board.repository.EntryRepository;
|
||||||
|
import de.bstly.board.repository.VoteRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class EntryManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntryRepository entryRepository;
|
||||||
|
@Autowired
|
||||||
|
private CommentManager commentManager;
|
||||||
|
@Autowired
|
||||||
|
private VoteManager voteManager;
|
||||||
|
@Autowired
|
||||||
|
private VoteRepository voteRepository;
|
||||||
|
private QEntry qEntry = QEntry.entry;
|
||||||
|
private QVote qVote = QVote.vote;
|
||||||
|
|
||||||
|
@Value("${bstly.board.ranking.gravity:1.8}")
|
||||||
|
private double GRAVITY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param page
|
||||||
|
* @param size
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Page<Entry> fetchByRanking(int page, int size) {
|
||||||
|
return entryRepository.findAll(
|
||||||
|
PageRequest.of(page, size, Sort.by(Order.desc("ranking"), Order.desc("created"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param page
|
||||||
|
* @param size
|
||||||
|
* @param order
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Page<Entry> fetchByDate(Instant date, int page, int size) {
|
||||||
|
return entryRepository.findAll(qEntry.created.before(date),
|
||||||
|
PageRequest.of(page, size, Sort.by(Order.desc("created"))));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param page
|
||||||
|
* @param size
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Page<RankedEntry> directFetchByRanking(Instant date, double gravity, int page,
|
||||||
|
int size) {
|
||||||
|
return entryRepository.findAllByRanking(date, gravity, PageRequest.of(page, size));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param entry
|
||||||
|
*/
|
||||||
|
public void applyMetadata(String username, Entry entry) {
|
||||||
|
if (!entry.getMetadata().containsKey("comments")) {
|
||||||
|
entry.getMetadata().put("comments", commentManager.count(entry.getId()));
|
||||||
|
}
|
||||||
|
if (!entry.getMetadata().containsKey("points")) {
|
||||||
|
entry.getMetadata().put("points", voteManager.getPoints(entry.getId(), Types.entry));
|
||||||
|
}
|
||||||
|
if (!entry.getMetadata().containsKey("vote")) {
|
||||||
|
entry.getMetadata().put("vote",
|
||||||
|
!voteRepository.exists(qVote.target.eq(entry.getId())
|
||||||
|
.and(qVote.targetType.eq(Types.entry)).and(qVote.type.eq(VoteType.up))
|
||||||
|
.and(qVote.author.eq(username))));
|
||||||
|
}
|
||||||
|
if (!entry.getMetadata().containsKey("unvote")) {
|
||||||
|
entry.getMetadata().put("unvote",
|
||||||
|
voteRepository.exists(qVote.target.eq(entry.getId())
|
||||||
|
.and(qVote.targetType.eq(Types.entry))
|
||||||
|
.and(qVote.type.eq(VoteType.up).and(qVote.author.eq(username)))));
|
||||||
|
}
|
||||||
|
if (!entry.getMetadata().containsKey("downvote")) {
|
||||||
|
entry.getMetadata().put("downvote",
|
||||||
|
!voteRepository.exists(qVote.target.eq(entry.getId())
|
||||||
|
.and(qVote.targetType.eq(Types.entry)).and(qVote.author.eq(username))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param entries
|
||||||
|
*/
|
||||||
|
public void applyMetadata(String username, List<Entry> entries) {
|
||||||
|
for (Entry entry : entries) {
|
||||||
|
applyMetadata(username, entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean exists(Long id) {
|
||||||
|
return entryRepository.existsById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Entry get(Long id) {
|
||||||
|
return entryRepository.findById(id).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param entry
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Entry save(Entry entry) {
|
||||||
|
return entryRepository.save(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param entryId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long getPoints(Long entryId) {
|
||||||
|
long upvotes = voteRepository.count(qVote.targetType.eq(Types.entry)
|
||||||
|
.and(qVote.type.eq(VoteType.up)).and(qVote.target.eq(entryId)));
|
||||||
|
long downvotes = voteRepository.count(qVote.targetType.eq(Types.entry)
|
||||||
|
.and(qVote.type.eq(VoteType.down)).and(qVote.target.eq(entryId)));
|
||||||
|
return upvotes - downvotes;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.businesslogic;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.temporal.ChronoField;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.time.temporal.TemporalAmount;
|
||||||
|
import java.time.temporal.TemporalUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class InstantHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant plus(Instant instant, TemporalAmount amount) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).plus(amount).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amountToAdd
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant plus(Instant instant, long amountToAdd, TemporalUnit unit) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).plus(amountToAdd, unit).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant minus(Instant instant, TemporalAmount amount) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).minus(amount).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amountToAdd
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant minus(Instant instant, long amountToAdd, TemporalUnit unit) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).minus(amountToAdd, unit)
|
||||||
|
.toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant truncate(Instant instant, TemporalUnit unit) {
|
||||||
|
if (ChronoUnit.YEARS.equals(unit)) {
|
||||||
|
instant = instant.truncatedTo(ChronoUnit.DAYS);
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)
|
||||||
|
.with(ChronoField.DAY_OF_YEAR, 1L).toInstant();
|
||||||
|
} else if (ChronoUnit.MONTHS.equals(unit)) {
|
||||||
|
instant = instant.truncatedTo(ChronoUnit.DAYS);
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)
|
||||||
|
.with(ChronoField.DAY_OF_MONTH, 1L).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).truncatedTo(unit).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
217
src/main/java/de/bstly/board/businesslogic/UserManager.java
Normal file
217
src/main/java/de/bstly/board/businesslogic/UserManager.java
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.businesslogic;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Entry;
|
||||||
|
import de.bstly.board.model.LocalUser;
|
||||||
|
import de.bstly.board.model.QEntry;
|
||||||
|
import de.bstly.board.model.QLocalUser;
|
||||||
|
import de.bstly.board.repository.EntryRepository;
|
||||||
|
import de.bstly.board.repository.LocalUserRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class UserManager implements UserDetailsService, SmartInitializingSingleton {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(UserManager.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private LocalUserRepository localUserRepository;
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Autowired
|
||||||
|
private EntryManager entryManager;
|
||||||
|
@Autowired
|
||||||
|
private EntryRepository entryRepository;
|
||||||
|
private QLocalUser qLocalUser = QLocalUser.localUser;
|
||||||
|
private QEntry qEntry = QEntry.entry;
|
||||||
|
|
||||||
|
@Value("${admin.password:}")
|
||||||
|
private String adminPassword;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see
|
||||||
|
* de.bstly.board.businesslogic.LocalUserManager#loadUserByUsername(java.lang.
|
||||||
|
* String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||||
|
LocalUser localUser = getByUsername(username);
|
||||||
|
|
||||||
|
if (localUser == null) {
|
||||||
|
throw new UsernameNotFoundException(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<GrantedAuthority> authorities = Lists.newArrayList();
|
||||||
|
if (localUser.getRoles() != null) {
|
||||||
|
for (String role : localUser.getRoles()) {
|
||||||
|
authorities.add(new SimpleGrantedAuthority(role));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new User(username, localUser.getPasswordHash(), authorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @see org.springframework.beans.factory.SmartInitializingSingleton#
|
||||||
|
* afterSingletonsInstantiated()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterSingletonsInstantiated() {
|
||||||
|
if (!localUserRepository.exists(qLocalUser.roles.contains("ROLE_ADMIN"))) {
|
||||||
|
if (!StringUtils.hasText(adminPassword)) {
|
||||||
|
adminPassword = RandomStringUtils.random(24, true, true);
|
||||||
|
logger.error("password for 'admin': "
|
||||||
|
+ adminPassword);
|
||||||
|
}
|
||||||
|
LocalUser admin = new LocalUser();
|
||||||
|
admin.setUsername("admin");
|
||||||
|
admin.setRoles(Lists.newArrayList("ROLE_ADMIN"));
|
||||||
|
admin.setPasswordHash(passwordEncoder.encode(adminPassword));
|
||||||
|
localUserRepository.save(admin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public LocalUser getByUsername(String username) {
|
||||||
|
return localUserRepository.findById(username).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param externalId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public LocalUser getByExternalId(String externalId) {
|
||||||
|
return localUserRepository.findOne(qLocalUser.externalId.eq(externalId)).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param authentication
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public LocalUser getByAuth(Authentication authentication) {
|
||||||
|
if (authentication != null) {
|
||||||
|
if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
||||||
|
UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;
|
||||||
|
return getByUsername(token.getName());
|
||||||
|
} else if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication;
|
||||||
|
String externalId = token.getAuthorizedClientRegistrationId()
|
||||||
|
+ "-"
|
||||||
|
+ token.getName();
|
||||||
|
LocalUser localUser = getByExternalId(externalId);
|
||||||
|
if (localUser == null) {
|
||||||
|
localUser = new LocalUser();
|
||||||
|
localUser.setExternalId(externalId);
|
||||||
|
String tmpUsername = token.getPrincipal().getAttribute("preferred_username");
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = token.getPrincipal().getAttribute("username");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = token.getPrincipal().getAttribute("name");
|
||||||
|
}
|
||||||
|
if (!StringUtils.hasText(tmpUsername)) {
|
||||||
|
tmpUsername = token.getName();
|
||||||
|
}
|
||||||
|
int count = 1;
|
||||||
|
String username = tmpUsername;
|
||||||
|
while (localUserRepository.existsById(username)) {
|
||||||
|
username = tmpUsername
|
||||||
|
+ "-"
|
||||||
|
+ count;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
localUser.setUsername(username);
|
||||||
|
localUser.setEmail(token.getPrincipal().getAttribute("email"));
|
||||||
|
|
||||||
|
String locale = token.getPrincipal().getAttribute("locale");
|
||||||
|
if (!StringUtils.hasText(locale)) {
|
||||||
|
locale = "en";
|
||||||
|
}
|
||||||
|
localUser.setLocale(locale);
|
||||||
|
|
||||||
|
String darkTheme = token.getPrincipal().getAttribute("darkTheme");
|
||||||
|
if (!StringUtils.hasText(darkTheme)) {
|
||||||
|
darkTheme = "false";
|
||||||
|
}
|
||||||
|
localUser.setDarkTheme(Boolean.valueOf(darkTheme));
|
||||||
|
|
||||||
|
localUser = localUserRepository.save(localUser);
|
||||||
|
}
|
||||||
|
return localUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param user
|
||||||
|
*/
|
||||||
|
public void applyMetadata(String username, LocalUser user) {
|
||||||
|
if (user.getUsername().equals(username) && !user.getMetadata().containsKey("self")) {
|
||||||
|
user.getMetadata().put("self", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user.getMetadata().containsKey("points")) {
|
||||||
|
long points = 0;
|
||||||
|
for (Entry entry : entryRepository.findAll(qEntry.author.eq(username))) {
|
||||||
|
points += entryManager.getPoints(entry.getId());
|
||||||
|
}
|
||||||
|
user.getMetadata().put("points", points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param localUser
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public LocalUser save(LocalUser localUser) {
|
||||||
|
return localUserRepository.save(localUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean exists(String username) {
|
||||||
|
return localUserRepository.existsById(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
69
src/main/java/de/bstly/board/businesslogic/VoteManager.java
Normal file
69
src/main/java/de/bstly/board/businesslogic/VoteManager.java
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.businesslogic;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.bstly.board.model.QVote;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
import de.bstly.board.repository.VoteRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class VoteManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VoteRepository voteRepository;
|
||||||
|
private QVote qVote = QVote.vote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param author
|
||||||
|
* @param targetType
|
||||||
|
* @param target
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Vote get(String author, Types targetType, Long target) {
|
||||||
|
return voteRepository.findOne(qVote.author.eq(author).and(qVote.targetType.eq(targetType))
|
||||||
|
.and(qVote.target.eq(target))).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param vote
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Vote save(Vote vote) {
|
||||||
|
return voteRepository.save(vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param vote
|
||||||
|
*/
|
||||||
|
public void delete(Vote vote) {
|
||||||
|
voteRepository.delete(vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param targetType
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Long getPoints(Long target, Types targetType) {
|
||||||
|
return voteRepository
|
||||||
|
.count(qVote.target.eq(target)
|
||||||
|
.and(qVote.targetType.eq(targetType).and(qVote.type.eq(VoteType.up))))
|
||||||
|
- voteRepository.count(qVote.target.eq(target)
|
||||||
|
.and(qVote.targetType.eq(targetType).and(qVote.type.eq(VoteType.down))));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.businesslogic.support;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.ZoneOffset;
|
||||||
|
import java.time.ZonedDateTime;
|
||||||
|
import java.time.temporal.ChronoField;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.time.temporal.TemporalAmount;
|
||||||
|
import java.time.temporal.TemporalUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class InstantHelper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant plus(Instant instant, TemporalAmount amount) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).plus(amount).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amountToAdd
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant plus(Instant instant, long amountToAdd, TemporalUnit unit) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).plus(amountToAdd, unit).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amount
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant minus(Instant instant, TemporalAmount amount) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).minus(amount).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param amountToAdd
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant minus(Instant instant, long amountToAdd, TemporalUnit unit) {
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).minus(amountToAdd, unit)
|
||||||
|
.toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param instant
|
||||||
|
* @param unit
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Instant truncate(Instant instant, TemporalUnit unit) {
|
||||||
|
if (ChronoUnit.YEARS.equals(unit)) {
|
||||||
|
instant = instant.truncatedTo(ChronoUnit.DAYS);
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)
|
||||||
|
.with(ChronoField.DAY_OF_YEAR, 1L).toInstant();
|
||||||
|
} else if (ChronoUnit.MONTHS.equals(unit)) {
|
||||||
|
instant = instant.truncatedTo(ChronoUnit.DAYS);
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC)
|
||||||
|
.with(ChronoField.DAY_OF_MONTH, 1L).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ZonedDateTime.ofInstant(instant, ZoneOffset.UTC).truncatedTo(unit).toInstant();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
113
src/main/java/de/bstly/board/controller/AuthenticationController.java
Executable file
113
src/main/java/de/bstly/board/controller/AuthenticationController.java
Executable file
@ -0,0 +1,113 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.ResolvableType;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth")
|
||||||
|
public class AuthenticationController extends BaseController {
|
||||||
|
|
||||||
|
private static String authorizationRequestBaseUri = "oauth2/authorization";
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ClientRegistrationRepository clientRegistrationRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
public Authentication me() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return auth;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
@GetMapping("external")
|
||||||
|
public List<Client> getExternalLoginUrls() {
|
||||||
|
List<Client> clients = Lists.newArrayList();
|
||||||
|
Iterable<ClientRegistration> clientRegistrations = null;
|
||||||
|
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository)
|
||||||
|
.as(Iterable.class);
|
||||||
|
if (type != ResolvableType.NONE
|
||||||
|
&& ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
|
||||||
|
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
clientRegistrations
|
||||||
|
.forEach(registration -> clients.add(new Client(registration.getRegistrationId(),
|
||||||
|
authorizationRequestBaseUri
|
||||||
|
+ "/"
|
||||||
|
+ registration.getRegistrationId())));
|
||||||
|
|
||||||
|
return clients;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class Client {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String loginUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id
|
||||||
|
* @param loginUrl
|
||||||
|
*/
|
||||||
|
public Client(String id, String loginUrl) {
|
||||||
|
super();
|
||||||
|
this.id = id;
|
||||||
|
this.loginUrl = loginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id the id to set
|
||||||
|
*/
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the loginUrl
|
||||||
|
*/
|
||||||
|
public String getLoginUrl() {
|
||||||
|
return loginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param loginUrl the loginUrl to set
|
||||||
|
*/
|
||||||
|
public void setLoginUrl(String loginUrl) {
|
||||||
|
this.loginUrl = loginUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
48
src/main/java/de/bstly/board/controller/BaseController.java
Normal file
48
src/main/java/de/bstly/board/controller/BaseController.java
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.UserManager;
|
||||||
|
import de.bstly.board.model.LocalUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager localUserManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected boolean authenticated() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return auth != null && auth.isAuthenticated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected String getCurrentUsername() {
|
||||||
|
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
return auth != null ? auth.getName() : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected LocalUser getLocalUser() {
|
||||||
|
return localUserManager.getByAuth(SecurityContextHolder.getContext().getAuthentication());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
132
src/main/java/de/bstly/board/controller/CommentController.java
Normal file
132
src/main/java/de/bstly/board/controller/CommentController.java
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.CommentManager;
|
||||||
|
import de.bstly.board.businesslogic.VoteManager;
|
||||||
|
import de.bstly.board.controller.support.EntityResponseStatusException;
|
||||||
|
import de.bstly.board.controller.support.RequestBodyErrors;
|
||||||
|
import de.bstly.board.controller.validation.CommentValidator;
|
||||||
|
import de.bstly.board.model.Comment;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/c")
|
||||||
|
public class CommentController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommentManager commentManager;
|
||||||
|
@Autowired
|
||||||
|
private CommentValidator commentValidator;
|
||||||
|
@Autowired
|
||||||
|
private VoteManager voteManager;
|
||||||
|
|
||||||
|
@Value("${bstly.board.size:30}")
|
||||||
|
private int SIZE;
|
||||||
|
@Value("${bstly.board.ranking.gravity:1.8}")
|
||||||
|
private double GRAVITY;
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping({ "/e/{target}", "/e/{target}/{parent}" })
|
||||||
|
public Page<Comment> rankedComments(@PathVariable("target") Long target,
|
||||||
|
@PathVariable("parent") Optional<Long> parent,
|
||||||
|
@RequestParam("page") Optional<Integer> pageParameter,
|
||||||
|
@RequestParam("size") Optional<Integer> sizeParameter,
|
||||||
|
@RequestParam("date") Optional<Instant> dateParameter,
|
||||||
|
@RequestParam("gravity") Optional<Double> gravityParameter) {
|
||||||
|
|
||||||
|
Page<Comment> comments = newComments(target, parent, pageParameter, sizeParameter,
|
||||||
|
dateParameter);
|
||||||
|
commentManager.applyMetadata(getCurrentUsername(), comments.getContent());
|
||||||
|
return comments;
|
||||||
|
|
||||||
|
// Page<Comment> comments = commentManager.fetchByRanking(target, parent.orElse(null),
|
||||||
|
// dateParameter.orElse(Instant.now()), gravityParameter.orElse(GRAVITY),
|
||||||
|
// pageParameter.orElse(0), sizeParameter.orElse(SIZE));
|
||||||
|
// return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping({ "/c/{target}", "/c/{target}/{parent}" })
|
||||||
|
public Long countComments(@PathVariable("target") Long target,
|
||||||
|
@PathVariable("parent") Optional<Long> parent) {
|
||||||
|
return commentManager.count(target, parent.orElse(null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping({ "/e/new/{target}", "/e/new/{target}/{parent}" })
|
||||||
|
public Page<Comment> newComments(@PathVariable("target") Long target,
|
||||||
|
@PathVariable("parent") Optional<Long> parent,
|
||||||
|
@RequestParam("page") Optional<Integer> pageParameter,
|
||||||
|
@RequestParam("size") Optional<Integer> sizeParameter,
|
||||||
|
@RequestParam("date") Optional<Instant> dateParameter) {
|
||||||
|
Page<Comment> comments = commentManager.fetchByDate(target, parent.orElse(null),
|
||||||
|
dateParameter.orElse(Instant.now()), pageParameter.orElse(0),
|
||||||
|
sizeParameter.orElse(SIZE));
|
||||||
|
commentManager.applyMetadata(getCurrentUsername(), comments.getContent());
|
||||||
|
return comments;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Comment getComment(@PathVariable("id") Long id) {
|
||||||
|
Comment comment = commentManager.get(id);
|
||||||
|
|
||||||
|
if (comment == null) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
commentManager.applyMetadata(getCurrentUsername(), comment);
|
||||||
|
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PostMapping()
|
||||||
|
public Comment createComment(@RequestBody Comment comment) {
|
||||||
|
RequestBodyErrors bindingResult = new RequestBodyErrors(comment);
|
||||||
|
commentValidator.validate(comment, bindingResult);
|
||||||
|
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(bindingResult.getAllErrors(),
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
comment.setCreated(Instant.now());
|
||||||
|
comment.setAuthor(getCurrentUsername());
|
||||||
|
comment = commentManager.save(comment);
|
||||||
|
|
||||||
|
Vote vote = new Vote();
|
||||||
|
vote.setTarget(comment.getId());
|
||||||
|
vote.setTargetType(Types.comment);
|
||||||
|
vote.setType(VoteType.up);
|
||||||
|
vote.setAuthor(getCurrentUsername());
|
||||||
|
voteManager.save(vote);
|
||||||
|
|
||||||
|
return comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
202
src/main/java/de/bstly/board/controller/DebugController.java
Normal file
202
src/main/java/de/bstly/board/controller/DebugController.java
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
|
import org.apache.commons.lang3.RandomUtils;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.EntryManager;
|
||||||
|
import de.bstly.board.model.Comment;
|
||||||
|
import de.bstly.board.model.Entry;
|
||||||
|
import de.bstly.board.model.EntryStatus;
|
||||||
|
import de.bstly.board.model.EntryType;
|
||||||
|
import de.bstly.board.model.LocalUser;
|
||||||
|
import de.bstly.board.model.QLocalUser;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
import de.bstly.board.repository.CommentRepository;
|
||||||
|
import de.bstly.board.repository.LocalUserRepository;
|
||||||
|
import de.bstly.board.repository.VoteRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/debug")
|
||||||
|
public class DebugController extends BaseController {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logger
|
||||||
|
*/
|
||||||
|
private Logger logger = LogManager.getLogger(DebugController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
@Autowired
|
||||||
|
private LocalUserRepository localUserRepository;
|
||||||
|
@Autowired
|
||||||
|
private CommentRepository commentRepository;
|
||||||
|
@Autowired
|
||||||
|
private VoteRepository voteRepository;
|
||||||
|
@Autowired
|
||||||
|
private EntryManager entryManager;
|
||||||
|
|
||||||
|
@Value("${debug.random.users:0}")
|
||||||
|
private int users;
|
||||||
|
@Value("${debug.random.minEntries:0}")
|
||||||
|
private int minEntries;
|
||||||
|
@Value("${debug.random.maxEntries:10}")
|
||||||
|
private int maxEntries;
|
||||||
|
@Value("${debug.random.entryAge:63115200}")
|
||||||
|
private long entryAge;
|
||||||
|
@Value("${debug.random.minComments:0}")
|
||||||
|
private int minComments;
|
||||||
|
@Value("${debug.random.maxComments:10}")
|
||||||
|
private int maxComments;
|
||||||
|
@Value("${debug.random.subCommentsFactor:0.5}")
|
||||||
|
private double subCommentsFactor;
|
||||||
|
@Value("${debug.random.subCommentsThresh:0.3}")
|
||||||
|
private double subCommentsThresh;
|
||||||
|
@Value("${debug.random.subCommentsDepth:2}")
|
||||||
|
private int subCommentsDepth;
|
||||||
|
@Value("${debug.random.minUpvotes:5}")
|
||||||
|
private int minUpvotes;
|
||||||
|
@Value("${debug.random.maxUpvotes:10}")
|
||||||
|
private int maxUpvotes;
|
||||||
|
@Value("${debug.random.minDownvotes:0}")
|
||||||
|
private int minDownvotes;
|
||||||
|
@Value("${debug.random.maxDownvotes:10}")
|
||||||
|
private int maxDownvotes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping("/random")
|
||||||
|
public void random() {
|
||||||
|
logger.warn("start random generation");
|
||||||
|
|
||||||
|
long userCount = localUserRepository
|
||||||
|
.count(QLocalUser.localUser.username.startsWith("user"));
|
||||||
|
|
||||||
|
for (long i = userCount; i < userCount + users; i++) {
|
||||||
|
LocalUser localUser = new LocalUser();
|
||||||
|
String username = "user" + i;
|
||||||
|
localUser.setUsername(username);
|
||||||
|
localUser.setPasswordHash(passwordEncoder.encode(username));
|
||||||
|
localUserRepository.save(localUser);
|
||||||
|
logger.trace("Created user: '" + username + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Created " + users + " users");
|
||||||
|
|
||||||
|
for (long id = 0; id <= userCount; id++) {
|
||||||
|
entries("user" + id, userCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.warn("finished random generation");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void entries(String username, long userCount) {
|
||||||
|
long numEntries = RandomUtils.nextLong(minEntries, maxEntries);
|
||||||
|
for (int i = 0; i < numEntries; i++) {
|
||||||
|
Entry entry = new Entry();
|
||||||
|
entry.setEntryType(EntryType.INTERN);
|
||||||
|
entry.setAuthor(username);
|
||||||
|
entry.setCreated(
|
||||||
|
Instant.now().minus(RandomUtils.nextLong(0, entryAge), ChronoUnit.SECONDS));
|
||||||
|
entry.setTitle(RandomStringUtils.randomAscii(RandomUtils.nextInt(10, 250)));
|
||||||
|
entry.setText(RandomStringUtils.randomAscii(RandomUtils.nextInt(0, 2500)));
|
||||||
|
entry.setEntryStatus(EntryStatus.NORMAL);
|
||||||
|
entry = entryManager.save(entry);
|
||||||
|
logger.trace("Created entry: '" + entry.getId() + "'");
|
||||||
|
comments(entry.getId(), entry.getCreated(), userCount);
|
||||||
|
votes(entry.getId(), Types.entry, userCount);
|
||||||
|
}
|
||||||
|
logger.info("Created " + numEntries + " entries of '" + username + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void comments(Long target, Instant date, long userCount) {
|
||||||
|
long numComments = RandomUtils.nextLong(minComments, maxComments);
|
||||||
|
logger.debug("Create " + numComments + " comments for '" + target + "'");
|
||||||
|
for (int i = 0; i < numComments; i++) {
|
||||||
|
Comment comment = new Comment();
|
||||||
|
comment.setTarget(target);
|
||||||
|
comment.setAuthor("user" + RandomUtils.nextLong(0, userCount));
|
||||||
|
comment.setText(RandomStringUtils.randomAscii(RandomUtils.nextInt(0, 2500)));
|
||||||
|
comment.setCreated(Instant.now()
|
||||||
|
.minus(RandomUtils.nextLong(0,
|
||||||
|
(Instant.now().toEpochMilli() - date.toEpochMilli()) / 1000),
|
||||||
|
ChronoUnit.SECONDS));
|
||||||
|
comment = commentRepository.save(comment);
|
||||||
|
logger.trace("Created comment: '" + comment.getId() + "'");
|
||||||
|
subComments(target, comment.getId(), comment.getCreated(), subCommentsFactor,
|
||||||
|
subCommentsThresh, 0, userCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void subComments(Long target, Long parent, Instant date, double factor, double thresh,
|
||||||
|
int depth, long userCount) {
|
||||||
|
if (depth < subCommentsDepth && RandomUtils.nextDouble(0, 1) < thresh) {
|
||||||
|
long numSubComments = RandomUtils.nextLong(0, Math.round(maxComments * factor));
|
||||||
|
logger.debug("Create " + numSubComments + " subComments for '" + parent + "'");
|
||||||
|
for (int i = 0; i < numSubComments; i++) {
|
||||||
|
Comment comment = new Comment();
|
||||||
|
comment.setTarget(target);
|
||||||
|
comment.setParent(parent);
|
||||||
|
comment.setAuthor("user" + RandomUtils.nextLong(0, userCount));
|
||||||
|
comment.setText(RandomStringUtils.randomAscii(RandomUtils.nextInt(0, 2500)));
|
||||||
|
comment.setCreated(Instant.now()
|
||||||
|
.minus(RandomUtils.nextLong(0,
|
||||||
|
(Instant.now().toEpochMilli() - date.toEpochMilli()) / 1000),
|
||||||
|
ChronoUnit.SECONDS));
|
||||||
|
comment = commentRepository.save(comment);
|
||||||
|
logger.trace("Created subComment: '" + comment.getId() + "'");
|
||||||
|
subComments(target, comment.getId(), comment.getCreated(), factor * 0.5,
|
||||||
|
thresh * 0.5, depth++, userCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void votes(Long target, Types targetType, long userCount) {
|
||||||
|
long numUpvotes = RandomUtils.nextLong(minUpvotes, maxUpvotes);
|
||||||
|
logger.debug("Create " + numUpvotes + " upvotes for '" + target + "'");
|
||||||
|
for (int i = 0; i < numUpvotes; i++) {
|
||||||
|
Vote upvote = new Vote();
|
||||||
|
upvote.setTarget(target);
|
||||||
|
upvote.setType(VoteType.up);
|
||||||
|
upvote.setTargetType(targetType);
|
||||||
|
upvote.setAuthor("user" + RandomUtils.nextLong(0, userCount));
|
||||||
|
upvote = voteRepository.save(upvote);
|
||||||
|
logger.trace("Created upvote: '" + upvote.getId() + "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
long numDownvotes = RandomUtils.nextLong(minDownvotes, maxDownvotes);
|
||||||
|
logger.debug("Create " + numDownvotes + " downvotes for '" + target + "'");
|
||||||
|
for (int i = 0; i < numDownvotes; i++) {
|
||||||
|
Vote downvote = new Vote();
|
||||||
|
downvote.setTarget(target);
|
||||||
|
downvote.setType(VoteType.down);
|
||||||
|
downvote.setTargetType(targetType);
|
||||||
|
downvote.setAuthor("user" + RandomUtils.nextLong(0, userCount));
|
||||||
|
downvote = voteRepository.save(downvote);
|
||||||
|
logger.trace("Created downvote: '" + downvote.getId() + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
152
src/main/java/de/bstly/board/controller/EntryController.java
Normal file
152
src/main/java/de/bstly/board/controller/EntryController.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.PageImpl;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.EntryManager;
|
||||||
|
import de.bstly.board.businesslogic.VoteManager;
|
||||||
|
import de.bstly.board.controller.support.EntityResponseStatusException;
|
||||||
|
import de.bstly.board.controller.support.RequestBodyErrors;
|
||||||
|
import de.bstly.board.controller.validation.EntryValidator;
|
||||||
|
import de.bstly.board.model.Entry;
|
||||||
|
import de.bstly.board.model.EntryStatus;
|
||||||
|
import de.bstly.board.model.RankedEntry;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/e")
|
||||||
|
public class EntryController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EntryManager entryManager;
|
||||||
|
@Autowired
|
||||||
|
private EntryValidator entryValidator;
|
||||||
|
@Autowired
|
||||||
|
private VoteManager voteManager;
|
||||||
|
|
||||||
|
@Value("${bstly.board.size:30}")
|
||||||
|
private int SIZE;
|
||||||
|
@Value("${bstly.board.ranking.gravity:1.8}")
|
||||||
|
private double GRAVITY;
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping()
|
||||||
|
public Page<Entry> rankedEntries(@RequestParam("page") Optional<Integer> pageParameter,
|
||||||
|
@RequestParam("size") Optional<Integer> sizeParameter,
|
||||||
|
@RequestParam("date") Optional<Instant> dateParameter,
|
||||||
|
@RequestParam("gravity") Optional<Double> gravityParameter) {
|
||||||
|
|
||||||
|
if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
|
||||||
|
sizeParameter = Optional.of(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<RankedEntry> entries = entryManager.directFetchByRanking(
|
||||||
|
dateParameter.orElse(Instant.now()), gravityParameter.orElse(GRAVITY),
|
||||||
|
pageParameter.orElse(0), sizeParameter.orElse(SIZE));
|
||||||
|
|
||||||
|
Page<Entry> transformed = new PageImpl<Entry>(
|
||||||
|
entries.getContent().stream().map(rankedEntry -> Entry.fromRankedEntry(rankedEntry))
|
||||||
|
.collect(Collectors.toList()),
|
||||||
|
entries.getPageable(), entries.getTotalElements());
|
||||||
|
|
||||||
|
entryManager.applyMetadata(getCurrentUsername(), transformed.getContent());
|
||||||
|
return transformed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("ranked")
|
||||||
|
public Page<Entry> rankedEntries(@RequestParam("page") Optional<Integer> pageParameter,
|
||||||
|
@RequestParam("size") Optional<Integer> sizeParameter) {
|
||||||
|
|
||||||
|
if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
|
||||||
|
sizeParameter = Optional.of(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<Entry> entries = entryManager.fetchByRanking(pageParameter.orElse(0),
|
||||||
|
sizeParameter.orElse(SIZE));
|
||||||
|
|
||||||
|
entryManager.applyMetadata(getCurrentUsername(), entries.getContent());
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/new")
|
||||||
|
public Page<Entry> newEntries(@RequestParam("page") Optional<Integer> pageParameter,
|
||||||
|
@RequestParam("size") Optional<Integer> sizeParameter,
|
||||||
|
@RequestParam("date") Optional<Instant> dateParameter) {
|
||||||
|
|
||||||
|
if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
|
||||||
|
sizeParameter = Optional.of(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
Page<Entry> entries = entryManager.fetchByDate(dateParameter.orElse(Instant.now()),
|
||||||
|
pageParameter.orElse(0), sizeParameter.orElse(SIZE));
|
||||||
|
entryManager.applyMetadata(getCurrentUsername(), entries.getContent());
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public Entry getEntry(@PathVariable("id") Long id) {
|
||||||
|
Entry entry = entryManager.get(id);
|
||||||
|
|
||||||
|
if (entry == null) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
entryManager.applyMetadata(getCurrentUsername(), entry);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PostMapping()
|
||||||
|
public Entry createEntry(@RequestBody Entry entry) {
|
||||||
|
RequestBodyErrors bindingResult = new RequestBodyErrors(entry);
|
||||||
|
entryValidator.validate(entry, bindingResult);
|
||||||
|
|
||||||
|
if (bindingResult.hasErrors()) {
|
||||||
|
throw new EntityResponseStatusException(bindingResult.getAllErrors(),
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.setCreated(Instant.now());
|
||||||
|
entry.setAuthor(getCurrentUsername());
|
||||||
|
entry.setEntryStatus(EntryStatus.NORMAL);
|
||||||
|
entry = entryManager.save(entry);
|
||||||
|
|
||||||
|
Vote vote = new Vote();
|
||||||
|
vote.setTarget(entry.getId());
|
||||||
|
vote.setType(VoteType.up);
|
||||||
|
vote.setTargetType(Types.entry);
|
||||||
|
vote.setAuthor(getCurrentUsername());
|
||||||
|
voteManager.save(vote);
|
||||||
|
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
79
src/main/java/de/bstly/board/controller/UserController.java
Normal file
79
src/main/java/de/bstly/board/controller/UserController.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.UserManager;
|
||||||
|
import de.bstly.board.controller.support.EntityResponseStatusException;
|
||||||
|
import de.bstly.board.model.LocalUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/u")
|
||||||
|
public class UserController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager userManager;
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping({ "", "/{username}" })
|
||||||
|
public LocalUser getUser(@PathVariable("username") Optional<String> usernameParameter) {
|
||||||
|
String username = usernameParameter.orElse(getCurrentUsername());
|
||||||
|
|
||||||
|
LocalUser user = userManager.getByUsername(username);
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.getUsername() != getCurrentUsername()) {
|
||||||
|
LocalUser otherUser = new LocalUser();
|
||||||
|
otherUser.setUsername(user.getUsername());
|
||||||
|
otherUser.setAbout(user.getAbout());
|
||||||
|
}
|
||||||
|
|
||||||
|
user.setPasswordHash(null);
|
||||||
|
|
||||||
|
userManager.applyMetadata(getCurrentUsername(), user);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PostMapping()
|
||||||
|
public LocalUser updateUser(@RequestBody LocalUser user) {
|
||||||
|
if (!getCurrentUsername().equals(user.getUsername())) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalUser orgUser = userManager.getByUsername(user.getUsername());
|
||||||
|
|
||||||
|
orgUser.setAbout(user.getAbout());
|
||||||
|
orgUser.setDarkTheme(user.isDarkTheme());
|
||||||
|
orgUser.setEmail(user.getEmail());
|
||||||
|
orgUser.setLocale(user.getLocale());
|
||||||
|
orgUser.setSettings(user.getSettings());
|
||||||
|
|
||||||
|
user = userManager.save(orgUser);
|
||||||
|
|
||||||
|
user.setPasswordHash(null);
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
143
src/main/java/de/bstly/board/controller/VoteController.java
Normal file
143
src/main/java/de/bstly/board/controller/VoteController.java
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.CommentManager;
|
||||||
|
import de.bstly.board.businesslogic.EntryManager;
|
||||||
|
import de.bstly.board.businesslogic.VoteManager;
|
||||||
|
import de.bstly.board.controller.support.EntityResponseStatusException;
|
||||||
|
import de.bstly.board.model.Types;
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
import de.bstly.board.model.VoteType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/v")
|
||||||
|
public class VoteController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private VoteManager voteManager;
|
||||||
|
@Autowired
|
||||||
|
private EntryManager entryManager;
|
||||||
|
@Autowired
|
||||||
|
private CommentManager commentManager;
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/e/{id}")
|
||||||
|
public long getEntryPoints(@PathVariable("id") Long id) {
|
||||||
|
if (!entryManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return voteManager.getPoints(id, Types.entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PutMapping("/e/{id}")
|
||||||
|
public void voteEntryUp(@PathVariable("id") Long id) {
|
||||||
|
if (!entryManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vote vote = voteManager.get(getCurrentUsername(), Types.entry, id);
|
||||||
|
if (vote == null) {
|
||||||
|
vote = new Vote();
|
||||||
|
vote.setTarget(id);
|
||||||
|
vote.setAuthor(getCurrentUsername());
|
||||||
|
vote.setType(VoteType.up);
|
||||||
|
vote.setTargetType(Types.entry);
|
||||||
|
voteManager.save(vote);
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.CREATED);
|
||||||
|
} else if (!VoteType.up.equals(vote.getType())) {
|
||||||
|
voteManager.delete(vote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@DeleteMapping("/e/{id}")
|
||||||
|
public void voteEntryDown(@PathVariable("id") Long id) {
|
||||||
|
if (!entryManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vote vote = voteManager.get(getCurrentUsername(), Types.entry, id);
|
||||||
|
if (vote == null) {
|
||||||
|
vote = new Vote();
|
||||||
|
vote.setTarget(id);
|
||||||
|
vote.setAuthor(getCurrentUsername());
|
||||||
|
vote.setType(VoteType.down);
|
||||||
|
vote.setTargetType(Types.entry);
|
||||||
|
voteManager.save(vote);
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.CREATED);
|
||||||
|
} else if (!VoteType.down.equals(vote.getType())) {
|
||||||
|
voteManager.delete(vote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@GetMapping("/c/{id}")
|
||||||
|
public long getCommentPoints(@PathVariable("id") Long id) {
|
||||||
|
if (!commentManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
return voteManager.getPoints(id, Types.comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@PutMapping("/c/{id}")
|
||||||
|
public void voteCommentUp(@PathVariable("id") Long id) {
|
||||||
|
if (!commentManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vote vote = voteManager.get(getCurrentUsername(), Types.comment, id);
|
||||||
|
if (vote == null) {
|
||||||
|
vote = new Vote();
|
||||||
|
vote.setTarget(id);
|
||||||
|
vote.setAuthor(getCurrentUsername());
|
||||||
|
vote.setType(VoteType.up);
|
||||||
|
vote.setTargetType(Types.comment);
|
||||||
|
voteManager.save(vote);
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.CREATED);
|
||||||
|
} else if (!VoteType.up.equals(vote.getType())) {
|
||||||
|
voteManager.delete(vote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("isAuthenticated()")
|
||||||
|
@DeleteMapping("/c/{id}")
|
||||||
|
public void voteCommentDown(@PathVariable("id") Long id) {
|
||||||
|
if (!commentManager.exists(id)) {
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vote vote = voteManager.get(getCurrentUsername(), Types.comment, id);
|
||||||
|
if (vote == null) {
|
||||||
|
vote = new Vote();
|
||||||
|
vote.setTarget(id);
|
||||||
|
vote.setAuthor(getCurrentUsername());
|
||||||
|
vote.setType(VoteType.down);
|
||||||
|
vote.setTargetType(Types.comment);
|
||||||
|
voteManager.save(vote);
|
||||||
|
throw new EntityResponseStatusException(HttpStatus.CREATED);
|
||||||
|
} else if (!VoteType.down.equals(vote.getType())) {
|
||||||
|
voteManager.delete(vote);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller.support;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ControllerExceptionHandler extends ResponseEntityExceptionHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param exception
|
||||||
|
* @param request
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@ExceptionHandler(value = { EntityResponseStatusException.class })
|
||||||
|
protected ResponseEntity<Object> handleResponseEntityStatusException(RuntimeException exception,
|
||||||
|
WebRequest request) {
|
||||||
|
EntityResponseStatusException entityResponseStatusException = (EntityResponseStatusException) exception;
|
||||||
|
return handleExceptionInternal(exception, entityResponseStatusException.getBody(),
|
||||||
|
new HttpHeaders(), entityResponseStatusException.getStatus(), request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller.support;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.springframework.core.NestedExceptionUtils;
|
||||||
|
import org.springframework.core.NestedRuntimeException;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class EntityResponseStatusException extends NestedRuntimeException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default serialVersionUID
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final HttpStatus status;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Object body;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param status
|
||||||
|
*/
|
||||||
|
public EntityResponseStatusException(HttpStatus status) {
|
||||||
|
this(null, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @param status
|
||||||
|
*/
|
||||||
|
public EntityResponseStatusException(@Nullable Object body, HttpStatus status) {
|
||||||
|
this(body, status, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param body
|
||||||
|
* @param status
|
||||||
|
* @param cause
|
||||||
|
*/
|
||||||
|
public EntityResponseStatusException(@Nullable Object body, HttpStatus status, @Nullable Throwable cause) {
|
||||||
|
super(null, cause);
|
||||||
|
Assert.notNull(status, "HttpStatus is required");
|
||||||
|
this.status = status;
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public HttpStatus getStatus() {
|
||||||
|
return this.status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Object getBody() {
|
||||||
|
return this.body;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
String msg = this.status + (this.body != null ? " \"" + this.body + "\"" : "");
|
||||||
|
return NestedExceptionUtils.buildMessage(msg, getCause());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller.support;
|
||||||
|
|
||||||
|
import org.springframework.lang.Nullable;
|
||||||
|
import org.springframework.validation.AbstractBindingResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class RequestBodyErrors extends AbstractBindingResult {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final Object target;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param target
|
||||||
|
* @param objectName
|
||||||
|
*/
|
||||||
|
public RequestBodyErrors(@Nullable Object target) {
|
||||||
|
super("request-body");
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.validation.AbstractBindingResult#getTarget()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Object getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see
|
||||||
|
* org.springframework.validation.AbstractBindingResult#getActualFieldValue(java
|
||||||
|
* .lang.String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Object getActualFieldValue(String field) {
|
||||||
|
// Not necessary
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller.validation;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.CommentManager;
|
||||||
|
import de.bstly.board.businesslogic.EntryManager;
|
||||||
|
import de.bstly.board.model.Comment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class CommentValidator implements Validator {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private CommentManager commentManager;
|
||||||
|
@Autowired
|
||||||
|
private EntryManager entryManager;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.validation.Validator#supports(java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return clazz.isAssignableFrom(Comment.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.validation.Validator#validate(java.lang.Object,
|
||||||
|
* org.springframework.validation.Errors)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
Comment comment = (Comment) target;
|
||||||
|
if (comment.getTarget() == null) {
|
||||||
|
errors.rejectValue("target", "REQUIRED");
|
||||||
|
} else if (!entryManager.exists(comment.getTarget())) {
|
||||||
|
errors.rejectValue("target", "INVALID");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (comment.getParent() != null) {
|
||||||
|
Comment parent = commentManager.get(comment.getParent());
|
||||||
|
if (parent == null || !parent.getTarget().equals(comment.getTarget())) {
|
||||||
|
errors.rejectValue("parent", "INVALID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!StringUtils.hasText(comment.getText())) {
|
||||||
|
errors.rejectValue("text", "REQUIRED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.controller.validation;
|
||||||
|
|
||||||
|
import org.apache.commons.validator.routines.UrlValidator;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
import org.springframework.validation.Errors;
|
||||||
|
import org.springframework.validation.Validator;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Entry;
|
||||||
|
import de.bstly.board.model.EntryType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class EntryValidator implements Validator {
|
||||||
|
|
||||||
|
private UrlValidator urlValidator = new UrlValidator();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.validation.Validator#supports(java.lang.Class)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return clazz.isAssignableFrom(Entry.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.validation.Validator#validate(java.lang.Object,
|
||||||
|
* org.springframework.validation.Errors)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void validate(Object target, Errors errors) {
|
||||||
|
Entry entry = (Entry) target;
|
||||||
|
if (!StringUtils.hasText(entry.getTitle())) {
|
||||||
|
errors.rejectValue("title", "REQUIRED");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.getEntryType() == null) {
|
||||||
|
errors.rejectValue("entrytype", "REQUIRED");
|
||||||
|
} else if (EntryType.LINK.equals(entry.getEntryType())) {
|
||||||
|
entry.setText(null);
|
||||||
|
if (!StringUtils.hasText(entry.getUrl())) {
|
||||||
|
errors.rejectValue("url", "REQUIRED");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StringUtils.hasText(entry.getUrl()) && !urlValidator.isValid(entry.getUrl())) {
|
||||||
|
errors.rejectValue("url", "INVALID");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
28
src/main/java/de/bstly/board/events/VotedEvent.java
Normal file
28
src/main/java/de/bstly/board/events/VotedEvent.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.events;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationEvent;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class VotedEvent extends ApplicationEvent {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param source
|
||||||
|
*/
|
||||||
|
public VotedEvent(Vote vote) {
|
||||||
|
super(vote);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
176
src/main/java/de/bstly/board/i18n/businesslogic/I18nManager.java
Normal file
176
src/main/java/de/bstly/board/i18n/businesslogic/I18nManager.java
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.i18n.businesslogic;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.springframework.beans.factory.SmartInitializingSingleton;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
import com.google.gson.JsonParser;
|
||||||
|
|
||||||
|
import de.bstly.board.i18n.model.I18n;
|
||||||
|
import de.bstly.board.i18n.repository.I18nRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class I18nManager implements SmartInitializingSingleton {
|
||||||
|
|
||||||
|
private Logger logger = LoggerFactory.getLogger(I18nManager.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private I18nRepository i18nRepository;
|
||||||
|
@Autowired
|
||||||
|
private ResourceLoader resourceLoader;
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public I18n get(String locale) {
|
||||||
|
return i18nRepository.findById(locale).orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public JsonObject getLabel(String locale) {
|
||||||
|
I18n i18n = get(locale);
|
||||||
|
if (i18n != null && StringUtils.hasText(i18n.getLabel())) {
|
||||||
|
JsonElement element = JsonParser.parseString(i18n.getLabel());
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
return element.getAsJsonObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<String> getLocales() {
|
||||||
|
return i18nRepository.findAll().stream().map(I18n::getLocale).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param dest
|
||||||
|
* @param src
|
||||||
|
*/
|
||||||
|
protected void extendJsonObject(JsonObject dest, JsonObject src) {
|
||||||
|
for (Entry<String, JsonElement> srcEntry : src.entrySet()) {
|
||||||
|
String srcKey = srcEntry.getKey();
|
||||||
|
JsonElement srcValue = srcEntry.getValue();
|
||||||
|
if (dest.has(srcKey)) {
|
||||||
|
JsonElement destValue = dest.get(srcKey);
|
||||||
|
if (destValue.isJsonObject() && srcValue.isJsonObject()) {
|
||||||
|
extendJsonObject(destValue.getAsJsonObject(), srcValue.getAsJsonObject());
|
||||||
|
} else {
|
||||||
|
dest.add(srcKey, srcValue);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
dest.add(srcKey, srcValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @param newLabel
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public I18n addLabel(String locale, JsonObject newLabel) {
|
||||||
|
JsonObject label = getLabel(locale);
|
||||||
|
|
||||||
|
if (label == null || label.size() == 0 || label.entrySet().isEmpty()) {
|
||||||
|
label = newLabel;
|
||||||
|
} else {
|
||||||
|
extendJsonObject(label, newLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
I18n i18n = new I18n();
|
||||||
|
i18n.setLocale(locale);
|
||||||
|
i18n.setLabel(gson.toJson(label));
|
||||||
|
|
||||||
|
return i18nRepository.save(i18n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @param label
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public I18n setLabel(String locale, JsonObject label) {
|
||||||
|
I18n i18n = new I18n();
|
||||||
|
i18n.setLocale(locale);
|
||||||
|
i18n.setLabel(gson.toJson(label));
|
||||||
|
|
||||||
|
return i18nRepository.save(i18n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
*/
|
||||||
|
public void deleteLabel(String locale) {
|
||||||
|
if (i18nRepository.existsById(locale)) {
|
||||||
|
i18nRepository.deleteById(locale);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @see org.springframework.beans.factory.SmartInitializingSingleton#
|
||||||
|
* afterSingletonsInstantiated()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void afterSingletonsInstantiated() {
|
||||||
|
try {
|
||||||
|
Resource resource = resourceLoader.getResource("classpath:label");
|
||||||
|
|
||||||
|
if (resource.exists()) {
|
||||||
|
File labelFolder = resource.getFile();
|
||||||
|
if (labelFolder.exists() && labelFolder.isDirectory()) {
|
||||||
|
for (File labelFile : labelFolder.listFiles()) {
|
||||||
|
JsonObject label = JsonParser
|
||||||
|
.parseReader(new FileReader(labelFile, StandardCharsets.UTF_8))
|
||||||
|
.getAsJsonObject();
|
||||||
|
|
||||||
|
String locale = labelFile.getName().replace(".json", "");
|
||||||
|
addLabel(locale, label);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("cannot read in label folder", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
108
src/main/java/de/bstly/board/i18n/controller/I18nController.java
Normal file
108
src/main/java/de/bstly/board/i18n/controller/I18nController.java
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.i18n.controller;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonIOException;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import de.bstly.board.controller.BaseController;
|
||||||
|
import de.bstly.board.i18n.businesslogic.I18nManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/i18n")
|
||||||
|
public class I18nController extends BaseController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private I18nManager i18nManager;
|
||||||
|
private Gson gson = new Gson();
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@GetMapping
|
||||||
|
public List<String> getLocales() {
|
||||||
|
return i18nManager.getLocales();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @param response
|
||||||
|
* @throws JsonIOException
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
@GetMapping("/{locale}")
|
||||||
|
public void getLabel(@PathVariable("locale") String locale, HttpServletResponse response)
|
||||||
|
throws JsonIOException, IOException {
|
||||||
|
JsonObject label = i18nManager.getLabel(locale);
|
||||||
|
if (label != null) {
|
||||||
|
response.setCharacterEncoding("utf-8");
|
||||||
|
gson.toJson(label, response.getWriter());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @param label
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PostMapping("/{locale}")
|
||||||
|
public void setLabel(@PathVariable("locale") String locale, @RequestBody Object label) {
|
||||||
|
JsonElement element = gson.toJsonTree(label);
|
||||||
|
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
i18nManager.setLabel(locale, element.getAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
* @param label
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@PutMapping("/{locale}")
|
||||||
|
public void addLabel(@PathVariable("locale") String locale, @RequestBody Object label) {
|
||||||
|
JsonElement element = gson.toJsonTree(label);
|
||||||
|
|
||||||
|
if (element != null && element.isJsonObject()) {
|
||||||
|
i18nManager.addLabel(locale, element.getAsJsonObject());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param locale
|
||||||
|
*/
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@DeleteMapping("/{locale}")
|
||||||
|
public void deleteLocale(@PathVariable("locale") String locale) {
|
||||||
|
i18nManager.deleteLabel(locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
64
src/main/java/de/bstly/board/i18n/model/I18n.java
Normal file
64
src/main/java/de/bstly/board/i18n/model/I18n.java
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.i18n.model;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.UniqueConstraint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "i18n", uniqueConstraints = @UniqueConstraint(columnNames = { "locale" }))
|
||||||
|
public class I18n {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Id
|
||||||
|
@Column(name = "locale", unique = true, nullable = false)
|
||||||
|
private String locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Lob
|
||||||
|
@Column(name = "label")
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the locale
|
||||||
|
*/
|
||||||
|
public String getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale the locale to set
|
||||||
|
*/
|
||||||
|
public void setLocale(String locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the label
|
||||||
|
*/
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param label the label to set
|
||||||
|
*/
|
||||||
|
public void setLabel(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.i18n.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.bstly.board.i18n.model.I18n;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author _bastler@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface I18nRepository
|
||||||
|
extends JpaRepository<I18n, String>, QuerydslPredicateExecutor<I18n> {
|
||||||
|
|
||||||
|
}
|
152
src/main/java/de/bstly/board/model/Comment.java
Normal file
152
src/main/java/de/bstly/board/model/Comment.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "comments")
|
||||||
|
@EntityListeners({ AuditingEntityListener.class })
|
||||||
|
public class Comment {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
private Long id;
|
||||||
|
@Column(name = "author", nullable = false)
|
||||||
|
private String author;
|
||||||
|
@Column(name = "created", nullable = false)
|
||||||
|
private Instant created;
|
||||||
|
@Column(name = "target", nullable = false)
|
||||||
|
private Long target;
|
||||||
|
@Column(name = "parent", nullable = true)
|
||||||
|
private Long parent;
|
||||||
|
@Lob
|
||||||
|
@Column(name = "text", nullable = false)
|
||||||
|
private String text;
|
||||||
|
@Transient
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id the id to set
|
||||||
|
*/
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the author
|
||||||
|
*/
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param author the author to set
|
||||||
|
*/
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the created
|
||||||
|
*/
|
||||||
|
public Instant getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param created the created to set
|
||||||
|
*/
|
||||||
|
public void setCreated(Instant created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the target
|
||||||
|
*/
|
||||||
|
public Long getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param target the target to set
|
||||||
|
*/
|
||||||
|
public void setTarget(Long target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the parent
|
||||||
|
*/
|
||||||
|
public Long getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param parent the parent to set
|
||||||
|
*/
|
||||||
|
public void setParent(Long parent) {
|
||||||
|
this.parent = parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the text
|
||||||
|
*/
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param text the text to set
|
||||||
|
*/
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the metadata
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
if (metadata == null) {
|
||||||
|
metadata = Maps.newHashMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param metadata the metadata to set
|
||||||
|
*/
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
222
src/main/java/de/bstly/board/model/Entry.java
Normal file
222
src/main/java/de/bstly/board/model/Entry.java
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
import javax.persistence.EnumType;
|
||||||
|
import javax.persistence.Enumerated;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "entries")
|
||||||
|
@EntityListeners({ AuditingEntityListener.class })
|
||||||
|
public class Entry {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "id", nullable = false)
|
||||||
|
private Long id;
|
||||||
|
@Column(name = "author", nullable = false)
|
||||||
|
private String author;
|
||||||
|
@Column(name = "created", nullable = false)
|
||||||
|
private Instant created;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "entry_type", nullable = false)
|
||||||
|
private EntryType entryType;
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(name = "entry_status", nullable = false, columnDefinition = "varchar(255) default 'NORMAL'")
|
||||||
|
private EntryStatus entryStatus;
|
||||||
|
@Column(name = "url")
|
||||||
|
private String url;
|
||||||
|
@Column(name = "title", nullable = false)
|
||||||
|
private String title;
|
||||||
|
@Lob
|
||||||
|
@Column(name = "text")
|
||||||
|
private String text;
|
||||||
|
@Transient
|
||||||
|
private Double ranking;
|
||||||
|
@Transient
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id the id to set
|
||||||
|
*/
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the author
|
||||||
|
*/
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param author the author to set
|
||||||
|
*/
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the created
|
||||||
|
*/
|
||||||
|
public Instant getCreated() {
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param created the created to set
|
||||||
|
*/
|
||||||
|
public void setCreated(Instant created) {
|
||||||
|
this.created = created;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the entryType
|
||||||
|
*/
|
||||||
|
public EntryType getEntryType() {
|
||||||
|
return entryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param entryType the entryType to set
|
||||||
|
*/
|
||||||
|
public void setEntryType(EntryType entryType) {
|
||||||
|
this.entryType = entryType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the entryStatus
|
||||||
|
*/
|
||||||
|
public EntryStatus getEntryStatus() {
|
||||||
|
return entryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param entryStatus the entryStatus to set
|
||||||
|
*/
|
||||||
|
public void setEntryStatus(EntryStatus entryStatus) {
|
||||||
|
this.entryStatus = entryStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the url
|
||||||
|
*/
|
||||||
|
public String getUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url the url to set
|
||||||
|
*/
|
||||||
|
public void setUrl(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the title
|
||||||
|
*/
|
||||||
|
public String getTitle() {
|
||||||
|
return title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param title the title to set
|
||||||
|
*/
|
||||||
|
public void setTitle(String title) {
|
||||||
|
this.title = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the text
|
||||||
|
*/
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param text the text to set
|
||||||
|
*/
|
||||||
|
public void setText(String text) {
|
||||||
|
this.text = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the ranking
|
||||||
|
*/
|
||||||
|
public Double getRanking() {
|
||||||
|
return ranking;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ranking the ranking to set
|
||||||
|
*/
|
||||||
|
public void setRanking(Double ranking) {
|
||||||
|
this.ranking = ranking;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the metadata
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
if (metadata == null) {
|
||||||
|
metadata = Maps.newHashMap();
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param metadata the metadata to set
|
||||||
|
*/
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param rankedEntry
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static Entry fromRankedEntry(RankedEntry rankedEntry) {
|
||||||
|
Entry entry = new Entry();
|
||||||
|
entry.setId(rankedEntry.getId());
|
||||||
|
entry.setAuthor(rankedEntry.getAuthor());
|
||||||
|
entry.setCreated(rankedEntry.getCreated());
|
||||||
|
entry.setEntryType(rankedEntry.getEntry_Type());
|
||||||
|
entry.setUrl(rankedEntry.getUrl());
|
||||||
|
entry.setTitle(rankedEntry.getTitle());
|
||||||
|
entry.setText(rankedEntry.getText());
|
||||||
|
entry.setRanking(rankedEntry.getRanking());
|
||||||
|
entry.getMetadata().put("points", rankedEntry.getPoints());
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
14
src/main/java/de/bstly/board/model/EntryStatus.java
Normal file
14
src/main/java/de/bstly/board/model/EntryStatus.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum EntryStatus {
|
||||||
|
|
||||||
|
NORMAL, ARCHIVED, PINNED
|
||||||
|
|
||||||
|
}
|
14
src/main/java/de/bstly/board/model/EntryType.java
Normal file
14
src/main/java/de/bstly/board/model/EntryType.java
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum EntryType {
|
||||||
|
|
||||||
|
DISCUSSION, INTERN, LINK, QUESTION
|
||||||
|
|
||||||
|
}
|
206
src/main/java/de/bstly/board/model/LocalUser.java
Normal file
206
src/main/java/de/bstly/board/model/LocalUser.java
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.persistence.CollectionTable;
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.ElementCollection;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Lob;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
import javax.persistence.Transient;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.LazyCollection;
|
||||||
|
import org.hibernate.annotations.LazyCollectionOption;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users")
|
||||||
|
@JsonInclude(Include.NON_EMPTY)
|
||||||
|
public class LocalUser {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@Column(name = "username", nullable = false)
|
||||||
|
private String username;
|
||||||
|
@Column(name = "external_id", nullable = true)
|
||||||
|
private String externalId;
|
||||||
|
@JsonIgnore
|
||||||
|
@Column(name = "password_hash", nullable = true)
|
||||||
|
private String passwordHash;
|
||||||
|
@ElementCollection
|
||||||
|
@LazyCollection(LazyCollectionOption.FALSE)
|
||||||
|
@CollectionTable(name = "users_roles")
|
||||||
|
private List<String> roles;
|
||||||
|
@Lob
|
||||||
|
@Column(name = "about", nullable = true)
|
||||||
|
private String about;
|
||||||
|
@Column(name = "email", nullable = true)
|
||||||
|
private String email;
|
||||||
|
@Column(name = "locale", nullable = false, columnDefinition = "varchar(255) default 'en'")
|
||||||
|
private String locale;
|
||||||
|
@Column(name = "dark_theme", columnDefinition = "boolean default false")
|
||||||
|
private boolean darkTheme;
|
||||||
|
@ElementCollection
|
||||||
|
@LazyCollection(LazyCollectionOption.FALSE)
|
||||||
|
@CollectionTable(name = "users_settings")
|
||||||
|
private Map<String, String> settings;
|
||||||
|
@Transient
|
||||||
|
private Map<String, Object> metadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the username
|
||||||
|
*/
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username the username to set
|
||||||
|
*/
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the externalId
|
||||||
|
*/
|
||||||
|
public String getExternalId() {
|
||||||
|
return externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param externalId the externalId to set
|
||||||
|
*/
|
||||||
|
public void setExternalId(String externalId) {
|
||||||
|
this.externalId = externalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the passwordHash
|
||||||
|
*/
|
||||||
|
public String getPasswordHash() {
|
||||||
|
return passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param passwordHash the passwordHash to set
|
||||||
|
*/
|
||||||
|
public void setPasswordHash(String passwordHash) {
|
||||||
|
this.passwordHash = passwordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the roles
|
||||||
|
*/
|
||||||
|
public List<String> getRoles() {
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param roles the roles to set
|
||||||
|
*/
|
||||||
|
public void setRoles(List<String> roles) {
|
||||||
|
this.roles = roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the about
|
||||||
|
*/
|
||||||
|
public String getAbout() {
|
||||||
|
return about;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param about the about to set
|
||||||
|
*/
|
||||||
|
public void setAbout(String about) {
|
||||||
|
this.about = about;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the email
|
||||||
|
*/
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param email the email to set
|
||||||
|
*/
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the locale
|
||||||
|
*/
|
||||||
|
public String getLocale() {
|
||||||
|
return locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param locale the locale to set
|
||||||
|
*/
|
||||||
|
public void setLocale(String locale) {
|
||||||
|
this.locale = locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the darkTheme
|
||||||
|
*/
|
||||||
|
public boolean isDarkTheme() {
|
||||||
|
return darkTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param darkTheme the darkTheme to set
|
||||||
|
*/
|
||||||
|
public void setDarkTheme(boolean darkTheme) {
|
||||||
|
this.darkTheme = darkTheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the settings
|
||||||
|
*/
|
||||||
|
public Map<String, String> getSettings() {
|
||||||
|
return settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param settings the settings to set
|
||||||
|
*/
|
||||||
|
public void setSettings(Map<String, String> settings) {
|
||||||
|
this.settings = settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the metadata
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getMetadata() {
|
||||||
|
if (metadata == null) {
|
||||||
|
metadata = Maps.newHashMap();
|
||||||
|
}
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param metadata the metadata to set
|
||||||
|
*/
|
||||||
|
public void setMetadata(Map<String, Object> metadata) {
|
||||||
|
this.metadata = metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
87
src/main/java/de/bstly/board/model/PersistentLogin.java
Normal file
87
src/main/java/de/bstly/board/model/PersistentLogin.java
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "persistent_logins")
|
||||||
|
public class PersistentLogin {
|
||||||
|
|
||||||
|
@Column(name = "username", length = 64, nullable = false)
|
||||||
|
private String username;
|
||||||
|
@Id
|
||||||
|
@Column(name = "series", length = 64)
|
||||||
|
private String series;
|
||||||
|
@Column(name = "token", length = 64, nullable = false)
|
||||||
|
private String token;
|
||||||
|
@Column(name = "last_used", nullable = false)
|
||||||
|
private Instant last_used;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the username
|
||||||
|
*/
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param username the username to set
|
||||||
|
*/
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the series
|
||||||
|
*/
|
||||||
|
public String getSeries() {
|
||||||
|
return series;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param series the series to set
|
||||||
|
*/
|
||||||
|
public void setSeries(String series) {
|
||||||
|
this.series = series;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the token
|
||||||
|
*/
|
||||||
|
public String getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param token the token to set
|
||||||
|
*/
|
||||||
|
public void setToken(String token) {
|
||||||
|
this.token = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the last_used
|
||||||
|
*/
|
||||||
|
public Instant getLast_used() {
|
||||||
|
return last_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param last_used the last_used to set
|
||||||
|
*/
|
||||||
|
public void setLast_used(Instant last_used) {
|
||||||
|
this.last_used = last_used;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
31
src/main/java/de/bstly/board/model/RankedEntry.java
Normal file
31
src/main/java/de/bstly/board/model/RankedEntry.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public interface RankedEntry {
|
||||||
|
|
||||||
|
Long getId();
|
||||||
|
|
||||||
|
String getAuthor();
|
||||||
|
|
||||||
|
Instant getCreated();
|
||||||
|
|
||||||
|
EntryType getEntry_Type();
|
||||||
|
|
||||||
|
String getUrl();
|
||||||
|
|
||||||
|
String getTitle();
|
||||||
|
|
||||||
|
String getText();
|
||||||
|
|
||||||
|
Double getRanking();
|
||||||
|
|
||||||
|
Long getPoints();
|
||||||
|
}
|
13
src/main/java/de/bstly/board/model/Types.java
Normal file
13
src/main/java/de/bstly/board/model/Types.java
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum Types {
|
||||||
|
|
||||||
|
comment, entry, user
|
||||||
|
}
|
108
src/main/java/de/bstly/board/model/Vote.java
Normal file
108
src/main/java/de/bstly/board/model/Vote.java
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
import javax.persistence.Column;
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.EntityListeners;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.GenerationType;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.Table;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
@Table(name = "votes")
|
||||||
|
@EntityListeners({ AuditingEntityListener.class })
|
||||||
|
public class Vote {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.AUTO)
|
||||||
|
@Column(name = "id")
|
||||||
|
private Long id;
|
||||||
|
@Column(name = "target", nullable = false)
|
||||||
|
private Long target;
|
||||||
|
@Column(name = "target_type", nullable = false)
|
||||||
|
private Types targetType;
|
||||||
|
@Column(name = "author", nullable = false)
|
||||||
|
private String author;
|
||||||
|
@Column(name = "type", nullable = false)
|
||||||
|
private VoteType type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the id
|
||||||
|
*/
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param id the id to set
|
||||||
|
*/
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the target
|
||||||
|
*/
|
||||||
|
public Long getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param target the target to set
|
||||||
|
*/
|
||||||
|
public void setTarget(Long target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the targetType
|
||||||
|
*/
|
||||||
|
public Types getTargetType() {
|
||||||
|
return targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param targetType the targetType to set
|
||||||
|
*/
|
||||||
|
public void setTargetType(Types targetType) {
|
||||||
|
this.targetType = targetType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the author
|
||||||
|
*/
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param author the author to set
|
||||||
|
*/
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the type
|
||||||
|
*/
|
||||||
|
public VoteType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param type the type to set
|
||||||
|
*/
|
||||||
|
public void setType(VoteType type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
src/main/java/de/bstly/board/model/VoteType.java
Normal file
12
src/main/java/de/bstly/board/model/VoteType.java
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public enum VoteType {
|
||||||
|
up, down
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.repository;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Comment;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface CommentRepository
|
||||||
|
extends JpaRepository<Comment, Long>, QuerydslPredicateExecutor<Comment> {
|
||||||
|
|
||||||
|
@Query(value = "SELECT comment.*, ranked.ranking FROM comments AS comment LEFT JOIN (SELECT comment.id, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / POW(TIMESTAMPDIFF(HOUR, comment.created, :date)+2,:gravity) AS ranking FROM comments AS comment LEFT JOIN (SELECT upvote.target,COUNT(upvote.id) AS count FROM votes as upvote WHERE upvote.type = 0 AND upvote.target_type = 0 GROUP BY upvote.target) AS upvote ON upvote.target = comment.id LEFT JOIN (SELECT downvote.target,COUNT(downvote.id) AS count FROM votes as downvote WHERE downvote.type = 1 GROUP BY downvote.target) AS downvote ON downvote.target = comment.id) as ranked on ranked.id = comment.id WHERE comment.target = :target AND parent IS NULL AND comment.created < :date ORDER BY ranked.ranking DESC, comment.created DESC", countQuery = "SELECT count(*) FROM comments as comment WHERE comment.target = :target AND parent IS NULL AND comment.created < :date", nativeQuery = true)
|
||||||
|
List<Comment> findAllByRankingAndParent(@Param("target") Long target,
|
||||||
|
@Param("date") Instant date, @Param("gravity") double gravity);
|
||||||
|
|
||||||
|
@Query(value = "SELECT comment.*, ranked.ranking FROM comments AS comment LEFT JOIN (SELECT comment.id, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / POW(TIMESTAMPDIFF(HOUR, comment.created, :date)+2,:gravity) AS ranking FROM comments AS comment LEFT JOIN (SELECT upvote.target,COUNT(upvote.id) AS count FROM votes as upvote WHERE upvote.type = 0 AND upvote.target_type = 0 GROUP BY upvote.target) AS upvote ON upvote.target = comment.id LEFT JOIN (SELECT downvote.target,COUNT(downvote.id) AS count FROM votes as downvote WHERE downvote.type = 1 GROUP BY downvote.target) AS downvote ON downvote.target = comment.id) as ranked on ranked.id = comment.id WHERE comment.target = :target AND parent = :parent AND comment.created < :date ORDER BY ranked.ranking DESC, comment.created DESC", countQuery = "SELECT count(*) FROM comments as comment WHERE comment.target = :target AND parent = :parent AND comment.created < :date", nativeQuery = true)
|
||||||
|
List<Comment> findAllByRankingAndParent(@Param("target") Long target,
|
||||||
|
@Param("parent") Long parent, @Param("date") Instant date,
|
||||||
|
@Param("gravity") double gravity);
|
||||||
|
|
||||||
|
@Query(value = "SELECT comment.*, ranked.ranking FROM comments AS comment LEFT JOIN (SELECT comment.id, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / POW(TIMESTAMPDIFF(HOUR, comment.created, :date)+2,:gravity) AS ranking FROM comments AS comment LEFT JOIN (SELECT upvote.target,COUNT(upvote.id) AS count FROM votes as upvote WHERE upvote.type = 0 AND upvote.target_type = 0 GROUP BY upvote.target) AS upvote ON upvote.target = comment.id LEFT JOIN (SELECT downvote.target,COUNT(downvote.id) AS count FROM votes as downvote WHERE downvote.type = 1 GROUP BY downvote.target) AS downvote ON downvote.target = comment.id) as ranked on ranked.id = comment.id WHERE comment.target = :target AND parent IS NULL AND comment.created < :date ORDER BY ranked.ranking DESC, comment.created DESC", countQuery = "SELECT count(*) FROM comments as comment WHERE comment.target = :target AND parent IS NULL AND comment.created < :date", nativeQuery = true)
|
||||||
|
Page<Comment> findAllByRankingAndParent(@Param("target") Long target,
|
||||||
|
@Param("date") Instant date, @Param("gravity") double gravity, Pageable pageable);
|
||||||
|
|
||||||
|
@Query(value = "SELECT comment.*, ranked.ranking FROM comments AS comment LEFT JOIN (SELECT comment.id, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / POW(TIMESTAMPDIFF(HOUR, comment.created, :date)+2,:gravity) AS ranking FROM comments AS comment LEFT JOIN (SELECT upvote.target,COUNT(upvote.id) AS count FROM votes as upvote WHERE upvote.type = 0 AND upvote.target_type = 0 GROUP BY upvote.target) AS upvote ON upvote.target = comment.id LEFT JOIN (SELECT downvote.target,COUNT(downvote.id) AS count FROM votes as downvote WHERE downvote.type = 1 GROUP BY downvote.target) AS downvote ON downvote.target = comment.id) as ranked on ranked.id = comment.id WHERE comment.target = :target AND parent = :parent AND comment.created < :date ORDER BY ranked.ranking DESC, comment.created DESC", countQuery = "SELECT count(*) FROM comments as comment WHERE comment.target = :target AND parent = :parent AND comment.created < :date", nativeQuery = true)
|
||||||
|
Page<Comment> findAllByRankingAndParent(@Param("target") Long target,
|
||||||
|
@Param("parent") Long parent, @Param("date") Instant date,
|
||||||
|
@Param("gravity") double gravity, Pageable pageable);
|
||||||
|
}
|
58
src/main/java/de/bstly/board/repository/EntryRepository.java
Normal file
58
src/main/java/de/bstly/board/repository/EntryRepository.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.repository;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.jpa.repository.Query;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.data.repository.query.Param;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Entry;
|
||||||
|
import de.bstly.board.model.RankedEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface EntryRepository
|
||||||
|
extends JpaRepository<Entry, Long>, QuerydslPredicateExecutor<Entry> {
|
||||||
|
|
||||||
|
static final String UPVOTES_QUERY = "SELECT upvote.target,COUNT(upvote.id) AS count FROM votes as upvote WHERE upvote.type = 0 AND upvote.target_type = 1 GROUP BY upvote.target";
|
||||||
|
|
||||||
|
static final String DOWNVOTES_QUERY = "SELECT downvote.target,COUNT(downvote.id) AS count FROM votes as downvote WHERE downvote.type = 1 AND downvote.target_type = 1 GROUP BY downvote.target";
|
||||||
|
|
||||||
|
static final String CALCULATION_QUERY = "SELECT entry.*, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) as points, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / POW(TIMESTAMPDIFF(HOUR, entry.created, :before)+2,:gravity) AS ranking FROM entries AS entry LEFT JOIN ("
|
||||||
|
+ UPVOTES_QUERY
|
||||||
|
+ ") AS upvote ON upvote.target = entry.id LEFT JOIN ("
|
||||||
|
+ DOWNVOTES_QUERY
|
||||||
|
+ ") AS downvote ON downvote.target = entry.id WHERE entry.created < :before AND entry.entry_status = 'NORMAL' ORDER BY ranking DESC, entry.created DESC";
|
||||||
|
|
||||||
|
static final String ARCHIVE_CALCULATION_QUERY = "SELECT entry.*, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) as points, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / POW(TIMESTAMPDIFF(HOUR, entry.created, :before)+2,:gravity) AS ranking FROM entries AS entry LEFT JOIN ("
|
||||||
|
+ UPVOTES_QUERY
|
||||||
|
+ ") AS upvote ON upvote.target = entry.id LEFT JOIN ("
|
||||||
|
+ DOWNVOTES_QUERY
|
||||||
|
+ ") AS downvote ON downvote.target = entry.id WHERE entry.created < :before ORDER BY ranking DESC, entry.created DESC";
|
||||||
|
|
||||||
|
static final String ADDITIONAL_QUERY = "SELECT entry.*, calculation.ranking, calculation.points FROM entries AS entry LEFT JOIN ("
|
||||||
|
+ CALCULATION_QUERY
|
||||||
|
+ ") as calculation on calculation.id = entry.id WHERE entry.created < :before ORDER BY calculation.ranking DESC, entry.created DESC";
|
||||||
|
|
||||||
|
static final String COUNT_QUERY = "SELECT count(*) FROM entries as entry WHERE entry.created < :before";
|
||||||
|
|
||||||
|
@Query(value = CALCULATION_QUERY, countQuery = COUNT_QUERY, nativeQuery = true)
|
||||||
|
Page<RankedEntry> findAllByRanking(@Param("before") Instant before,
|
||||||
|
@Param("gravity") double gravity, Pageable pageable);
|
||||||
|
|
||||||
|
@Query(value = ARCHIVE_CALCULATION_QUERY, countQuery = COUNT_QUERY, nativeQuery = true)
|
||||||
|
Page<RankedEntry> findAllByRankingArchive(@Param("before") Instant before,
|
||||||
|
@Param("gravity") double gravity, Pageable pageable);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.bstly.board.model.LocalUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface LocalUserRepository
|
||||||
|
extends JpaRepository<LocalUser, String>, QuerydslPredicateExecutor<LocalUser> {
|
||||||
|
|
||||||
|
}
|
20
src/main/java/de/bstly/board/repository/VoteRepository.java
Normal file
20
src/main/java/de/bstly/board/repository/VoteRepository.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import de.bstly.board.model.Vote;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Repository
|
||||||
|
public interface VoteRepository extends JpaRepository<Vote, Long>, QuerydslPredicateExecutor<Vote> {
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.UserManager;
|
||||||
|
import de.bstly.board.model.LocalUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Lurkars
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class OAuth2AuthenticationSuccessHandler
|
||||||
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager localUserManager;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
|
||||||
|
Authentication authentication) throws IOException, ServletException {
|
||||||
|
LocalUser localUser = localUserManager.getByAuth(authentication);
|
||||||
|
|
||||||
|
User user = new User(localUser.getUsername(), "", authentication.getAuthorities());
|
||||||
|
|
||||||
|
UsernamePasswordAuthenticationToken newAuthentication = new UsernamePasswordAuthenticationToken(
|
||||||
|
user, null, authentication.getAuthorities());
|
||||||
|
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(newAuthentication);
|
||||||
|
|
||||||
|
handle(request, response, newAuthentication);
|
||||||
|
clearAuthenticationAttributes(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
111
src/main/java/de/bstly/board/security/SecurityConfig.java
Executable file
111
src/main/java/de/bstly/board/security/SecurityConfig.java
Executable file
@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package de.bstly.board.security;
|
||||||
|
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.crypto.argon2.Argon2PasswordEncoder;
|
||||||
|
import org.springframework.security.web.authentication.HttpStatusEntryPoint;
|
||||||
|
import org.springframework.security.web.authentication.RememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
|
||||||
|
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import de.bstly.board.businesslogic.UserManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author monitoring@bstly.de
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
||||||
|
public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManager localUserManager;
|
||||||
|
@Autowired
|
||||||
|
private OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
|
||||||
|
@Autowired
|
||||||
|
private DataSource dataSource;
|
||||||
|
@Value("${loginUrl:/login}")
|
||||||
|
private String loginUrl;
|
||||||
|
@Value("${loginTargetUrl:/}")
|
||||||
|
private String loginTargetUrl;
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* @see org.springframework.security.config.annotation.web.configuration.
|
||||||
|
* WebSecurityConfigurerAdapter#configure(org.springframework.security.config.
|
||||||
|
* annotation.web.builders.HttpSecurity)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void configure(HttpSecurity http) throws Exception {
|
||||||
|
|
||||||
|
oAuth2AuthenticationSuccessHandler.setDefaultTargetUrl(loginTargetUrl);
|
||||||
|
|
||||||
|
http
|
||||||
|
// crsf
|
||||||
|
.csrf().disable()
|
||||||
|
// anonymous
|
||||||
|
.anonymous().disable()
|
||||||
|
// login
|
||||||
|
.formLogin().loginPage("/login").defaultSuccessUrl(loginTargetUrl)
|
||||||
|
.failureHandler(new SimpleUrlAuthenticationFailureHandler(loginUrl
|
||||||
|
+ "?error"))
|
||||||
|
.and()
|
||||||
|
// remember me
|
||||||
|
.rememberMe().rememberMeServices(rememberMeServices()).and()
|
||||||
|
// logout
|
||||||
|
.logout().logoutUrl("/logout")
|
||||||
|
.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK))
|
||||||
|
.and()
|
||||||
|
// exception
|
||||||
|
.exceptionHandling()
|
||||||
|
.defaultAuthenticationEntryPointFor(
|
||||||
|
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
|
||||||
|
new AntPathRequestMatcher("/api/**"))
|
||||||
|
.and()
|
||||||
|
// oidc
|
||||||
|
.oauth2Login().successHandler(oAuth2AuthenticationSuccessHandler)
|
||||||
|
.failureHandler(new SimpleUrlAuthenticationFailureHandler(loginUrl
|
||||||
|
+ "?externalError"))
|
||||||
|
.loginPage("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean(name = "passwordEncoder")
|
||||||
|
public Argon2PasswordEncoder passwordEncoder() {
|
||||||
|
return new Argon2PasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PersistentTokenRepository persistentTokenRepository() {
|
||||||
|
JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
|
||||||
|
tokenRepository.setDataSource(dataSource);
|
||||||
|
return tokenRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RememberMeServices rememberMeServices() {
|
||||||
|
PersistentTokenBasedRememberMeServices rememberMeServices = new PersistentTokenBasedRememberMeServices(
|
||||||
|
"remember-me", localUserManager, persistentTokenRepository());
|
||||||
|
return rememberMeServices;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
46
src/main/resources/messages.properties
Normal file
46
src/main/resources/messages.properties
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# index
|
||||||
|
login=Login
|
||||||
|
login.error=Credentials not valid.
|
||||||
|
login.external=Login with <strong>{0}</strong>
|
||||||
|
logout=Logout
|
||||||
|
pagination.first=First
|
||||||
|
pagination.prev=Previous
|
||||||
|
pagination.next=Next
|
||||||
|
pagination.last=Last
|
||||||
|
username=Username
|
||||||
|
password=Password
|
||||||
|
rememberMe=Remember Me
|
||||||
|
settings=Settings
|
||||||
|
submit=Submit
|
||||||
|
top=Top
|
||||||
|
new=New
|
||||||
|
|
||||||
|
comments={0} comments
|
||||||
|
|
||||||
|
|
||||||
|
entry.title=title
|
||||||
|
entry.url=url
|
||||||
|
entry.text=text
|
||||||
|
entry.type=type
|
||||||
|
entry.vote=vote
|
||||||
|
entry.unvote=unvote
|
||||||
|
entry.votes={0} points
|
||||||
|
entry.createdBy=by <a href="/u/{0}">{0}</a>
|
||||||
|
|
||||||
|
entryType.LINK=link
|
||||||
|
entryType.DISCUSSION=discussion
|
||||||
|
entryType.QUESTION=question
|
||||||
|
entryType.INTERN=intern
|
||||||
|
|
||||||
|
entryType.LINK.bicon=bi-link-45deg
|
||||||
|
entryType.DISCUSSION.bicon=bi-chat-text
|
||||||
|
entryType.QUESTION.bicon=bi-question-square
|
||||||
|
entryType.INTERN.bicon=bi-shield-lock
|
||||||
|
|
||||||
|
REQUIRED.entry.title=title is required
|
||||||
|
REQUIRED.entry.type=type is required
|
||||||
|
REQUIRED.entry.url=url is required
|
||||||
|
INVALID.entry.url=url is invalid
|
||||||
|
|
||||||
|
entries.empty=No entries found.
|
||||||
|
submit.info=Create a new entry.
|
Loading…
Reference in New Issue
Block a user