This commit is contained in:
_Bastler 2021-10-07 14:00:40 +02:00
parent 50c2776f19
commit db572e6f2b
27 changed files with 916 additions and 304 deletions

View File

@ -10,7 +10,7 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>11</java.version> <java.version>11</java.version>
<revision>0.4.3-SNAPSHOT</revision> <revision>0.5.0-SNAPSHOT</revision>
</properties> </properties>
<parent> <parent>

View File

@ -16,6 +16,7 @@ import org.springframework.stereotype.Component;
import com.querydsl.jpa.impl.JPAQueryFactory; import com.querydsl.jpa.impl.JPAQueryFactory;
import de.bstly.board.model.Comment; import de.bstly.board.model.Comment;
import de.bstly.board.model.FlaggedStatus;
import de.bstly.board.model.QComment; import de.bstly.board.model.QComment;
import de.bstly.board.model.QEntry; import de.bstly.board.model.QEntry;
import de.bstly.board.model.QVote; import de.bstly.board.model.QVote;
@ -38,6 +39,9 @@ public class CommentManager {
private VoteRepository voteRepository; private VoteRepository voteRepository;
@Autowired @Autowired
private VoteManager voteManager; private VoteManager voteManager;
@Autowired
private FlagManager flagManager;
private QComment qComment = QComment.comment; private QComment qComment = QComment.comment;
private QVote qVote = QVote.vote; private QVote qVote = QVote.vote;
private QEntry qEntry = QEntry.entry; private QEntry qEntry = QEntry.entry;
@ -46,45 +50,62 @@ public class CommentManager {
* Fetch by date. * Fetch by date.
* *
* @param username the username * @param username the username
* @param target the target * @param target the target
* @param parent the parent * @param parent the parent
* @param date the date * @param date the date
* @param page the page * @param page the page
* @param size the size * @param size the size
* @param desc the desc * @param desc the desc
* @return the page * @return the page
*/ */
public Page<Comment> fetchByDate(String username, Long target, Long parent, Instant date, public Page<Comment> fetchByDate(String username, Long target, Long parent, Instant date,
int page, int size, boolean desc) { int page, int size, boolean desc) {
Sort sort = Sort.by(desc ? Order.desc("created") : Order.asc("created")); Sort sort = Sort.by(desc ? Order.desc("created") : Order.asc("created"));
if (parent == null) { if (parent == null) {
return commentRepository.findAll( return commentRepository
qComment.target.eq(target).and(qComment.parent.isNull()) .findAll(
.and(qComment.created.before(date).or(qComment.author.eq(username))), qComment.target.eq(target).and(qComment.parent.isNull())
PageRequest.of(page, size, sort)); .and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
.and(qComment.created.before(date)
.or(qComment.author.eq(username))),
PageRequest.of(page, size, sort));
} }
return commentRepository.findAll(qComment.target.eq(target).and(qComment.parent.eq(parent)) return commentRepository.findAll(qComment.target.eq(target).and(qComment.parent.eq(parent))
.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
.and(qComment.created.before(date)), PageRequest.of(page, size, sort)); .and(qComment.created.before(date)), PageRequest.of(page, size, sort));
} }
/**
* Fetch flagged.
*
* @param page the page
* @param size the size
* @param asc the asc
* @return the page
*/
public Page<Comment> fetchFlagged(int page, int size, boolean asc) {
Sort sort = Sort.by(asc ? Order.asc("created") : Order.desc("created"));
return commentRepository.findAll(qComment.flaggedStatus.eq(FlaggedStatus.FLAGGED),
PageRequest.of(page, size, sort));
}
/** /**
* Fetch by username. * Fetch by username.
* *
* @param username the username * @param username the username
* @param orElse the or else * @param date the date
* @param date the date * @param page the page
* @param page the page * @param size the size
* @param size the size * @param asc the asc
* @param asc the asc
* @return the page * @return the page
*/ */
public Page<Comment> fetchByUsername(String username, Long orElse, Instant date, int page, public Page<Comment> fetchByUsername(String username, Instant date, int page, int size,
int size, boolean asc) { boolean asc) {
Sort sort = Sort.by(asc ? Order.asc("created") : Order.desc("created")); Sort sort = Sort.by(asc ? Order.asc("created") : Order.desc("created"));
return commentRepository.findAll( return commentRepository.findAll(qComment.author.equalsIgnoreCase(username)
qComment.author.equalsIgnoreCase(username).and(qComment.created.before(date)), .and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
PageRequest.of(page, size, sort)); .and(qComment.created.before(date)), PageRequest.of(page, size, sort));
} }
/** /**
@ -100,6 +121,7 @@ public class CommentManager {
} }
return commentRepository.count(qComment.target.eq(target).and(qComment.parent.eq(parent)) return commentRepository.count(qComment.target.eq(target).and(qComment.parent.eq(parent))
.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
.and(qComment.created.before(Instant.now()))); .and(qComment.created.before(Instant.now())));
} }
@ -110,16 +132,17 @@ public class CommentManager {
* @return the long * @return the long
*/ */
public Long count(Long target) { public Long count(Long target) {
return commentRepository return commentRepository.count(
.count(qComment.target.eq(target).and(qComment.created.before(Instant.now()))); qComment.target.eq(target).and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL))
.and(qComment.created.before(Instant.now())));
} }
/** /**
* Apply metadata. * Apply metadata.
* *
* @param username the username * @param username the username
* @param comment the comment * @param comment the comment
* @param ignore the ignore * @param ignore the ignore
*/ */
public void applyMetadata(String username, Comment comment, List<String> ignore) { public void applyMetadata(String username, Comment comment, List<String> ignore) {
@ -160,6 +183,16 @@ public class CommentManager {
.and(qVote.author.equalsIgnoreCase(username)))); .and(qVote.author.equalsIgnoreCase(username))));
} }
if (!username.equals(comment.getAuthor()) && !ignore.contains("flag")) {
comment.getMetadata().put("flag",
flagManager.get(username, comment.getId(), Types.comment) == null);
}
if (!username.equals(comment.getAuthor()) && !ignore.contains("unflag")) {
comment.getMetadata().put("unflag",
flagManager.get(username, comment.getId(), Types.comment) != null);
}
if (!ignore.contains("entry")) { if (!ignore.contains("entry")) {
comment.getMetadata().put("entry", jpaQueryFactory.selectFrom(qEntry) comment.getMetadata().put("entry", jpaQueryFactory.selectFrom(qEntry)
.where(qEntry.id.eq(comment.getTarget())).select(qEntry.title).fetchOne()); .where(qEntry.id.eq(comment.getTarget())).select(qEntry.title).fetchOne());
@ -170,8 +203,8 @@ public class CommentManager {
* Apply metadata. * Apply metadata.
* *
* @param username the username * @param username the username
* @param entries the entries * @param entries the entries
* @param ignore the ignore * @param ignore the ignore
*/ */
public void applyMetadata(String username, List<Comment> entries, List<String> ignore) { public void applyMetadata(String username, List<Comment> entries, List<String> ignore) {
for (Comment comment : entries) { for (Comment comment : entries) {

View File

@ -17,6 +17,8 @@ import com.google.common.collect.Lists;
import de.bstly.board.model.Bookmarks; import de.bstly.board.model.Bookmarks;
import de.bstly.board.model.Entry; import de.bstly.board.model.Entry;
import de.bstly.board.model.EntryStatus;
import de.bstly.board.model.FlaggedStatus;
import de.bstly.board.model.QEntry; import de.bstly.board.model.QEntry;
import de.bstly.board.model.QVote; import de.bstly.board.model.QVote;
import de.bstly.board.model.RankedEntry; import de.bstly.board.model.RankedEntry;
@ -43,6 +45,8 @@ public class EntryManager {
private BookmarksManager bookmarksManager; private BookmarksManager bookmarksManager;
@Autowired @Autowired
private SettingsManager settingsManager; private SettingsManager settingsManager;
@Autowired
private FlagManager flagManager;
private QEntry qEntry = QEntry.entry; private QEntry qEntry = QEntry.entry;
@ -95,10 +99,25 @@ public class EntryManager {
* @return the page * @return the page
*/ */
public Page<Entry> fetchByDate(Instant date, int page, int size) { public Page<Entry> fetchByDate(Instant date, int page, int size) {
return entryRepository.findAll(qEntry.created.before(date), return entryRepository.findAll(
qEntry.created.before(date).and(qEntry.entryStatus.eq(EntryStatus.NORMAL)),
PageRequest.of(page, size, Sort.by(Order.desc("created")))); PageRequest.of(page, size, Sort.by(Order.desc("created"))));
} }
/**
* Fetch flagged.
*
* @param page the page
* @param size the size
* @param asc the asc
* @return the page
*/
public Page<Entry> fetchFlagged(int page, int size, boolean asc) {
Sort sort = Sort.by(asc ? Order.asc("created") : Order.desc("created"));
return entryRepository.findAll(qEntry.flaggedStatus.ne(FlaggedStatus.NORMAL),
PageRequest.of(page, size, sort));
}
/** /**
* Fetch by user. * Fetch by user.
* *
@ -155,8 +174,13 @@ public class EntryManager {
entry.getMetadata().put("points", voteManager.getPoints(entry.getId(), Types.entry)); entry.getMetadata().put("points", voteManager.getPoints(entry.getId(), Types.entry));
} }
if (!ignore.contains("bookmarked")) { if (!ignore.contains("bookmark")) {
entry.getMetadata().put("bookmarked", entry.getMetadata().put("bookmark",
!bookmarksManager.hasEntry(username, entry.getId()));
}
if (!ignore.contains("removeBookmark")) {
entry.getMetadata().put("removeBookmark",
bookmarksManager.hasEntry(username, entry.getId())); bookmarksManager.hasEntry(username, entry.getId()));
} }
@ -174,6 +198,16 @@ public class EntryManager {
.and(qVote.author.equalsIgnoreCase(username)))); .and(qVote.author.equalsIgnoreCase(username))));
} }
if (!username.equals(entry.getAuthor()) && !ignore.contains("flag")) {
entry.getMetadata().put("flag",
flagManager.get(username, entry.getId(), Types.entry) == null);
}
if (!username.equals(entry.getAuthor()) && !ignore.contains("unflag")) {
entry.getMetadata().put("unflag",
flagManager.get(username, entry.getId(), Types.entry) != null);
}
if (voteRepository if (voteRepository
.exists(qVote.target.eq(entry.getId()).and(qVote.targetType.eq(Types.entry)) .exists(qVote.target.eq(entry.getId()).and(qVote.targetType.eq(Types.entry))
.and(qVote.author.equalsIgnoreCase(username)))) { .and(qVote.author.equalsIgnoreCase(username)))) {

View File

@ -0,0 +1,219 @@
/**
*
*/
package de.bstly.board.businesslogic;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import de.bstly.board.model.Comment;
import de.bstly.board.model.Entry;
import de.bstly.board.model.Flag;
import de.bstly.board.model.FlaggedStatus;
import de.bstly.board.model.QComment;
import de.bstly.board.model.QFlag;
import de.bstly.board.model.Types;
import de.bstly.board.repository.CommentRepository;
import de.bstly.board.repository.FlagRepository;
/**
* The Class FlagManager.
*/
@Component
public class FlagManager {
@Autowired
private FlagRepository flagRepository;
@Autowired
private SettingsManager settingsManager;
@Autowired
private EntryManager entryManager;
@Autowired
private CommentManager commentManager;
@Autowired
private CommentRepository commentRepository;
private QFlag qFlag = QFlag.flag;
private QComment qComment = QComment.comment;
/**
* Gets the.
*
* @param author the author
* @param target the target
* @param targetType the target type
* @return the flag
*/
public Flag get(String author, Long target, Types targetType) {
return flagRepository.findOne(qFlag.author.eq(author).and(qFlag.targetType.eq(targetType))
.and(qFlag.target.eq(target))).orElse(null);
}
/**
* Save.
*
* @param flag the flag
* @return the flag
*/
public Flag save(Flag flag) {
return flagRepository.save(flag);
}
/**
* Delete.
*
* @param flag the flag
*/
public void delete(Flag flag) {
flagRepository.delete(flag);
}
/**
* Delete by target.
*
* @param target the target
* @param targetType the target type
*/
public void deleteByTarget(Long target, Types targetType) {
for (Flag flag : flagRepository
.findAll(qFlag.target.eq(target).and(qFlag.targetType.eq(targetType)))) {
delete(flag);
}
}
/**
* Gets the flags.
*
* @param target the target
* @param targetType the target type
* @return the flags
*/
public long getFlags(Long target, Types targetType) {
return flagRepository.count(qFlag.target.eq(target).and(qFlag.targetType.eq(targetType)));
}
/**
* Check flag status.
*
* @param target the target
* @param targetType the target type
*/
public void checkFlagStatus(Long target, Types targetType) {
switch (targetType) {
case comment:
checkCommentFlagStatus(target);
break;
case entry:
checkEntryFlagStatus(target);
break;
case user:
break;
default:
break;
}
}
/**
* Check comment flag status.
*
* @param target the target
*/
public void checkCommentFlagStatus(Long target) {
Assert.isTrue(commentManager.exists(target), "Comment not exists: '"
+ target
+ "'!");
Comment comment = commentManager.get(target);
if (getFlags(target, Types.comment) >= settingsManager.getFlahThresh()) {
if (!FlaggedStatus.FLAGGED.equals(comment.getFlaggedStatus())) {
flagComment(comment);
}
} else if (FlaggedStatus.FLAGGED.equals(comment.getFlaggedStatus())) {
unflagComment(comment);
}
}
/**
* Flag comment.
*
* @param comment the comment
*/
protected void flagComment(Comment comment) {
comment.setFlaggedStatus(FlaggedStatus.FLAGGED);
comment = commentManager.save(comment);
hideSubcomments(comment);
}
/**
* Hide subcomments.
*
* @param comment the comment
*/
protected void hideSubcomments(Comment comment) {
for (Comment subcomment : commentRepository.findAll(qComment.parent.eq(comment.getId())
.and(qComment.flaggedStatus.eq(FlaggedStatus.NORMAL)))) {
subcomment.setFlaggedStatus(FlaggedStatus.HIDDEN);
subcomment = commentManager.save(subcomment);
hideSubcomments(subcomment);
}
}
/**
* Unflag comment.
*
* @param comment the comment
*/
protected void unflagComment(Comment comment) {
comment.setFlaggedStatus(FlaggedStatus.NORMAL);
comment = commentManager.save(comment);
unhideSubcomments(comment);
}
/**
* Unhide subcomments.
*
* @param comment the comment
*/
protected void unhideSubcomments(Comment comment) {
for (Comment subcomment : commentRepository.findAll(qComment.parent.eq(comment.getId())
.and(qComment.flaggedStatus.eq(FlaggedStatus.HIDDEN)))) {
subcomment.setFlaggedStatus(FlaggedStatus.NORMAL);
subcomment = commentManager.save(subcomment);
hideSubcomments(subcomment);
}
}
/**
* Check entry flag status.
*
* @param target the target
*/
public void checkEntryFlagStatus(Long target) {
Assert.isTrue(entryManager.exists(target), "Entry not exists: '"
+ target
+ "'!");
Entry entry = entryManager.get(target);
if (getFlags(target, Types.entry) >= settingsManager.getFlahThresh()) {
if (!FlaggedStatus.FLAGGED.equals(entry.getFlaggedStatus())) {
entry.setFlaggedStatus(FlaggedStatus.FLAGGED);
entryManager.save(entry);
}
} else if (FlaggedStatus.FLAGGED.equals(entry.getFlaggedStatus())) {
entry.setFlaggedStatus(FlaggedStatus.NORMAL);
entryManager.save(entry);
}
}
/**
* Unflag.
*
* @param target the target
* @param targetType the target type
*/
public void unflag(Long target, Types targetType) {
deleteByTarget(target, targetType);
checkFlagStatus(target, targetType);
}
}

View File

@ -20,6 +20,8 @@ public class SettingsManager {
private long COMMENT_CHANGE_PERIDO; private long COMMENT_CHANGE_PERIDO;
@Value("${bstly.board.unvoteThresh:10}") @Value("${bstly.board.unvoteThresh:10}")
private long UNVOTE_THRESH; private long UNVOTE_THRESH;
@Value("${bstly.board.flagThresh:3}")
private long FLAG_THRESH;
/** /**
* Gets the gravity. * Gets the gravity.
@ -56,4 +58,13 @@ public class SettingsManager {
public long getUnvoteThresh() { public long getUnvoteThresh() {
return UNVOTE_THRESH; return UNVOTE_THRESH;
} }
/**
* Gets the flah thresh.
*
* @return the flah thresh
*/
public long getFlahThresh() {
return FLAG_THRESH;
}
} }

View File

@ -71,6 +71,39 @@ public class UserManager implements UserDetailsService, SmartInitializingSinglet
/* /*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String) * @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/ */
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/*
* @see org.springframework.security.core.userdetails.UserDetailsService#loadUserByUsername(java.lang.String)
*/
/* /*
* @see org.springframework.security.core.userdetails.UserDetailsService# * @see org.springframework.security.core.userdetails.UserDetailsService#
* loadUserByUsername(java.lang.String) * loadUserByUsername(java.lang.String)
@ -116,6 +149,39 @@ public class UserManager implements UserDetailsService, SmartInitializingSinglet
/* /*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated() * @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/ */
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/*
* @see org.springframework.beans.factory.SmartInitializingSingleton#afterSingletonsInstantiated()
*/
/* /*
* @see org.springframework.beans.factory.SmartInitializingSingleton# * @see org.springframework.beans.factory.SmartInitializingSingleton#
* afterSingletonsInstantiated() * afterSingletonsInstantiated()

View File

@ -27,11 +27,11 @@ public class VoteManager {
* Gets the. * Gets the.
* *
* @param author the author * @param author the author
* @param targetType the target type
* @param target the target * @param target the target
* @param targetType the target type
* @return the vote * @return the vote
*/ */
public Vote get(String author, Types targetType, Long target) { public Vote get(String author, Long target, Types targetType) {
return voteRepository.findOne(qVote.author.eq(author).and(qVote.targetType.eq(targetType)) return voteRepository.findOne(qVote.author.eq(author).and(qVote.targetType.eq(targetType))
.and(qVote.target.eq(target))).orElse(null); .and(qVote.target.eq(target))).orElse(null);
} }

View File

@ -54,12 +54,12 @@ public class CommentController extends BaseController {
/** /**
* Fetch by date. * Fetch by date.
* *
* @param target the target * @param target the target
* @param parent the parent * @param parent the parent
* @param pageParameter the page parameter * @param pageParameter the page parameter
* @param sizeParameter the size parameter * @param sizeParameter the size parameter
* @param dateParameter the date parameter * @param dateParameter the date parameter
* @param descParameter the desc parameter * @param descParameter the desc parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@ -72,11 +72,11 @@ public class CommentController extends BaseController {
@RequestParam("date") Optional<Instant> dateParameter, @RequestParam("date") Optional<Instant> dateParameter,
@RequestParam("desc") Optional<Boolean> descParameter, @RequestParam("desc") Optional<Boolean> descParameter,
@RequestParam("ignore") Optional<List<String>> ignoreParameter) { @RequestParam("ignore") Optional<List<String>> ignoreParameter) {
if (sizeParameter.isPresent() && sizeParameter.get() > 100) { if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
sizeParameter = Optional.of(100); sizeParameter = Optional.of(100);
} }
if (dateParameter.isPresent() && dateParameter.get().isAfter(Instant.now())) { if (dateParameter.isPresent() && dateParameter.get().isAfter(Instant.now())) {
dateParameter = Optional.of(Instant.now()); dateParameter = Optional.of(Instant.now());
} }
@ -92,34 +92,32 @@ public class CommentController extends BaseController {
/** /**
* Fetch by username. * Fetch by username.
* *
* @param username the username * @param username the username
* @param parent the parent * @param pageParameter the page parameter
* @param pageParameter the page parameter * @param sizeParameter the size parameter
* @param sizeParameter the size parameter * @param dateParameter the date parameter
* @param dateParameter the date parameter * @param ascParameter the asc parameter
* @param ascParameter the asc parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@GetMapping({ "/byuser/{username}" }) @GetMapping({ "/byuser/{username}" })
public Page<Comment> fetchByUsername(@PathVariable("username") String username, public Page<Comment> fetchByUsername(@PathVariable("username") String username,
@PathVariable("parent") Optional<Long> parent,
@RequestParam("page") Optional<Integer> pageParameter, @RequestParam("page") Optional<Integer> pageParameter,
@RequestParam("size") Optional<Integer> sizeParameter, @RequestParam("size") Optional<Integer> sizeParameter,
@RequestParam("date") Optional<Instant> dateParameter, @RequestParam("date") Optional<Instant> dateParameter,
@RequestParam("asc") Optional<Boolean> ascParameter, @RequestParam("asc") Optional<Boolean> ascParameter,
@RequestParam("ignore") Optional<List<String>> ignoreParameter) { @RequestParam("ignore") Optional<List<String>> ignoreParameter) {
if (sizeParameter.isPresent() && sizeParameter.get() > 100) { if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
sizeParameter = Optional.of(100); sizeParameter = Optional.of(100);
} }
if (dateParameter.isPresent() && dateParameter.get().isAfter(Instant.now())) { if (dateParameter.isPresent() && dateParameter.get().isAfter(Instant.now())) {
dateParameter = Optional.of(Instant.now()); dateParameter = Optional.of(Instant.now());
} }
Page<Comment> comments = commentManager.fetchByUsername(username, parent.orElse(null), Page<Comment> comments = commentManager.fetchByUsername(username,
dateParameter.orElse(Instant.now()), pageParameter.orElse(0), dateParameter.orElse(Instant.now()), pageParameter.orElse(0),
sizeParameter.orElse(settingsManager.getPageSize()), ascParameter.orElse(false)); sizeParameter.orElse(settingsManager.getPageSize()), ascParameter.orElse(false));
List<String> ignore = ignoreParameter.orElse(Lists.newArrayList()); List<String> ignore = ignoreParameter.orElse(Lists.newArrayList());
@ -144,7 +142,7 @@ public class CommentController extends BaseController {
/** /**
* Gets the comment. * Gets the comment.
* *
* @param id the id * @param id the id
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the comment * @return the comment
*/ */
@ -166,7 +164,7 @@ public class CommentController extends BaseController {
/** /**
* Creates the comment. * Creates the comment.
* *
* @param comment the comment * @param comment the comment
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the comment * @return the comment
*/ */
@ -203,7 +201,7 @@ public class CommentController extends BaseController {
/** /**
* Update comment. * Update comment.
* *
* @param comment the comment * @param comment the comment
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the comment * @return the comment
*/ */

View File

@ -63,11 +63,11 @@ public class EntryController extends BaseController {
/** /**
* Fetch by ranking. * Fetch by ranking.
* *
* @param pageParameter the page parameter * @param pageParameter the page parameter
* @param sizeParameter the size parameter * @param sizeParameter the size parameter
* @param dateParameter the date parameter * @param dateParameter the date parameter
* @param gravityParameter the gravity parameter * @param gravityParameter the gravity parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@ -110,9 +110,9 @@ public class EntryController extends BaseController {
/** /**
* Fetch by date. * Fetch by date.
* *
* @param pageParameter the page parameter * @param pageParameter the page parameter
* @param sizeParameter the size parameter * @param sizeParameter the size parameter
* @param dateParameter the date parameter * @param dateParameter the date parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@ -142,11 +142,11 @@ public class EntryController extends BaseController {
/** /**
* Fetch by comments. * Fetch by comments.
* *
* @param pageParameter the page parameter * @param pageParameter the page parameter
* @param sizeParameter the size parameter * @param sizeParameter the size parameter
* @param dateParameter the date parameter * @param dateParameter the date parameter
* @param gravityParameter the gravity parameter * @param gravityParameter the gravity parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@PreAuthorize("isAuthenticated()") @PreAuthorize("isAuthenticated()")
@ -183,9 +183,9 @@ public class EntryController extends BaseController {
/** /**
* Fetch by last. * Fetch by last.
* *
* @param pageParameter the page parameter * @param pageParameter the page parameter
* @param sizeParameter the size parameter * @param sizeParameter the size parameter
* @param dateParameter the date parameter * @param dateParameter the date parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@ -216,11 +216,11 @@ public class EntryController extends BaseController {
/** /**
* Fetch by user. * Fetch by user.
* *
* @param username the username * @param username the username
* @param pageParameter the page parameter * @param pageParameter the page parameter
* @param sizeParameter the size parameter * @param sizeParameter the size parameter
* @param dateParameter the date parameter * @param dateParameter the date parameter
* @param ascParameter the asc parameter * @param ascParameter the asc parameter
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the page * @return the page
*/ */
@ -253,7 +253,7 @@ public class EntryController extends BaseController {
/** /**
* Gets the entry. * Gets the entry.
* *
* @param id the id * @param id the id
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the entry * @return the entry
*/ */
@ -277,7 +277,7 @@ public class EntryController extends BaseController {
/** /**
* Creates the entry. * Creates the entry.
* *
* @param entry the entry * @param entry the entry
* @param ignoreParameter the ignore parameter * @param ignoreParameter the ignore parameter
* @return the entry * @return the entry
*/ */

View File

@ -0,0 +1,132 @@
/**
*
*/
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.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.FlagManager;
import de.bstly.board.controller.support.EntityResponseStatusException;
import de.bstly.board.model.Flag;
import de.bstly.board.model.Types;
/**
* The Class FlagController.
*/
@RestController
@RequestMapping("/flags")
public class FlagController extends BaseController {
@Autowired
private FlagManager flagManager;
@Autowired
private EntryManager entryManager;
@Autowired
private CommentManager commentManager;
/**
* Flag entry.
*
* @param id the id
*/
@PreAuthorize("isAuthenticated()")
@PutMapping("/entry/{id}")
public void flagEntry(@PathVariable("id") Long id) {
if (!entryManager.exists(id)) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
Flag flag = flagManager.get(getCurrentUsername(), id, Types.entry);
if (flag != null) {
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
}
flag = new Flag();
flag.setTarget(id);
flag.setAuthor(getCurrentUsername());
flag.setTargetType(Types.entry);
flagManager.save(flag);
flagManager.checkEntryFlagStatus(id);
throw new EntityResponseStatusException(HttpStatus.CREATED);
}
/**
* Unflag entry.
*
* @param id the id
*/
@PreAuthorize("isAuthenticated()")
@DeleteMapping("/entry/{id}")
public void unflagEntry(@PathVariable("id") Long id) {
if (!entryManager.exists(id)) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
Flag flag = flagManager.get(getCurrentUsername(), id, Types.entry);
if (flag == null) {
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
}
flagManager.delete(flag);
flagManager.checkEntryFlagStatus(id);
}
/**
* Flag comment.
*
* @param id the id
*/
@PreAuthorize("isAuthenticated()")
@PutMapping("/comment/{id}")
public void flagComment(@PathVariable("id") Long id) {
if (!commentManager.exists(id)) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
Flag flag = flagManager.get(getCurrentUsername(), id, Types.comment);
if (flag != null) {
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
}
flag = new Flag();
flag.setTarget(id);
flag.setAuthor(getCurrentUsername());
flag.setTargetType(Types.comment);
flagManager.save(flag);
flagManager.checkCommentFlagStatus(id);
throw new EntityResponseStatusException(HttpStatus.CREATED);
}
/**
* Unflag comment.
*
* @param id the id
*/
@PreAuthorize("isAuthenticated()")
@DeleteMapping("/comment/{id}")
public void unflagComment(@PathVariable("id") Long id) {
if (!commentManager.exists(id)) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
Flag flag = flagManager.get(getCurrentUsername(), id, Types.comment);
if (flag == null) {
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
}
flagManager.delete(flag);
flagManager.checkCommentFlagStatus(id);
}
}

View File

@ -3,38 +3,134 @@
*/ */
package de.bstly.board.controller; package de.bstly.board.controller;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.DeleteMapping; 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.PathVariable;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import de.bstly.board.businesslogic.CommentManager; import de.bstly.board.businesslogic.CommentManager;
import de.bstly.board.businesslogic.EntryManager; import de.bstly.board.businesslogic.EntryManager;
import de.bstly.board.businesslogic.FlagManager;
import de.bstly.board.businesslogic.SettingsManager;
import de.bstly.board.businesslogic.UserManager; import de.bstly.board.businesslogic.UserManager;
import de.bstly.board.controller.support.EntityResponseStatusException; import de.bstly.board.controller.support.EntityResponseStatusException;
import de.bstly.board.model.Comment;
import de.bstly.board.model.Entry;
import de.bstly.board.model.LocalUser; import de.bstly.board.model.LocalUser;
import de.bstly.board.model.Types;
/** /**
* The Class ModerationController. * The Class ModerationController.
*/ */
@RestController @RestController
@RequestMapping("/moderation") @RequestMapping("/moderation")
public class ModerationController { public class ModerationController extends BaseController {
@Autowired @Autowired
private CommentManager commentManager; private CommentManager commentManager;
@Autowired @Autowired
private EntryManager entryManager; private EntryManager entryManager;
@Autowired @Autowired
private UserManager userManager; private UserManager userManager;
@Autowired
private FlagManager flagManager;
@Autowired
private SettingsManager settingsManager;
/**
* Gets the flagged comments.
*
* @param pageParameter the page parameter
* @param sizeParameter the size parameter
* @param ascParameter the asc parameter
* @param ignoreParameter the ignore parameter
* @return the flagged comments
*/
@PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_MOD')")
@GetMapping("/flags/comments")
public Page<Comment> getFlaggedComments(@RequestParam("page") Optional<Integer> pageParameter,
@RequestParam("size") Optional<Integer> sizeParameter,
@RequestParam("asc") Optional<Boolean> ascParameter,
@RequestParam("ignore") Optional<List<String>> ignoreParameter) {
if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
sizeParameter = Optional.of(100);
}
Page<Comment> comments = commentManager.fetchFlagged(pageParameter.orElse(0),
sizeParameter.orElse(settingsManager.getPageSize()), ascParameter.orElse(false));
List<String> ignore = ignoreParameter.orElse(Lists.newArrayList("flag", "unflag"));
commentManager.applyMetadata(getCurrentUsername(), comments.getContent(), ignore);
return comments;
}
/**
* Gets the flagged entries.
*
* @param pageParameter the page parameter
* @param sizeParameter the size parameter
* @param ascParameter the asc parameter
* @param ignoreParameter the ignore parameter
* @return the flagged entries
*/
@PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_MOD')")
@GetMapping("/flags/entries")
public Page<Entry> getFlaggedEntries(@RequestParam("page") Optional<Integer> pageParameter,
@RequestParam("size") Optional<Integer> sizeParameter,
@RequestParam("asc") Optional<Boolean> ascParameter,
@RequestParam("ignore") Optional<List<String>> ignoreParameter) {
if (sizeParameter.isPresent() && sizeParameter.get() > 100) {
sizeParameter = Optional.of(100);
}
Page<Entry> entries = entryManager.fetchFlagged(pageParameter.orElse(0),
sizeParameter.orElse(settingsManager.getPageSize()), ascParameter.orElse(false));
List<String> ignore = ignoreParameter.orElse(Lists.newArrayList("flag", "unflag", "bookmark", "removeBookmark"));
entryManager.applyMetadata(getCurrentUsername(), userManager.getKarma(getCurrentUsername()),
entries.getContent(), ignore);
return entries;
}
/**
* Unflag comment.
*
* @param id the id
*/
@PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_MOD')")
@DeleteMapping("/flags/comment/{id}")
public void unflagComment(@PathVariable("id") Long id) {
if (!commentManager.exists(id)) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
flagManager.unflag(id, Types.comment);
}
/**
* Unflag entry.
*
* @param id the id
*/
@PreAuthorize("hasRole('ROLE_ADMIN') || hasRole('ROLE_MOD')")
@DeleteMapping("/flags/entry/{id}")
public void unflagEntry(@PathVariable("id") Long id) {
if (!entryManager.exists(id)) {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
}
flagManager.unflag(id, Types.entry);
}
/** /**
* Delete comment. * Delete comment.

View File

@ -69,7 +69,7 @@ public class VoteController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY); throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
} }
Vote vote = voteManager.get(getCurrentUsername(), Types.entry, id); Vote vote = voteManager.get(getCurrentUsername(), id, Types.entry);
if (vote == null) { if (vote == null) {
vote = new Vote(); vote = new Vote();
vote.setTarget(id); vote.setTarget(id);
@ -99,7 +99,7 @@ public class VoteController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.FORBIDDEN); throw new EntityResponseStatusException(HttpStatus.FORBIDDEN);
} }
Vote vote = voteManager.get(getCurrentUsername(), Types.entry, id); Vote vote = voteManager.get(getCurrentUsername(), id, Types.entry);
if (vote == null) { if (vote == null) {
vote = new Vote(); vote = new Vote();
vote.setTarget(id); vote.setTarget(id);
@ -125,7 +125,7 @@ public class VoteController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY); throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
} }
Vote vote = voteManager.get(getCurrentUsername(), Types.entry, id); Vote vote = voteManager.get(getCurrentUsername(), id, Types.entry);
if (vote == null) { if (vote == null) {
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED); throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
} }
@ -161,7 +161,7 @@ public class VoteController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY); throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
} }
Vote vote = voteManager.get(getCurrentUsername(), Types.comment, id); Vote vote = voteManager.get(getCurrentUsername(), id, Types.comment);
if (vote == null) { if (vote == null) {
vote = new Vote(); vote = new Vote();
vote.setTarget(id); vote.setTarget(id);
@ -187,7 +187,7 @@ public class VoteController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY); throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
} }
Vote vote = voteManager.get(getCurrentUsername(), Types.comment, id); Vote vote = voteManager.get(getCurrentUsername(), id, Types.comment);
if (vote == null) { if (vote == null) {
vote = new Vote(); vote = new Vote();
vote.setTarget(id); vote.setTarget(id);
@ -213,7 +213,7 @@ public class VoteController extends BaseController {
throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY); throw new EntityResponseStatusException(HttpStatus.UNPROCESSABLE_ENTITY);
} }
Vote vote = voteManager.get(getCurrentUsername(), Types.comment, id); Vote vote = voteManager.get(getCurrentUsername(), id, Types.comment);
if (vote == null) { if (vote == null) {
throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED); throw new EntityResponseStatusException(HttpStatus.NOT_MODIFIED);
} }

View File

@ -9,6 +9,8 @@ import java.util.Map;
import javax.persistence.Column; import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EntityListeners; import javax.persistence.EntityListeners;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType; import javax.persistence.GenerationType;
import javax.persistence.Id; import javax.persistence.Id;
@ -43,6 +45,9 @@ public class Comment {
@Lob @Lob
@Column(name = "text", nullable = false) @Column(name = "text", nullable = false)
private String text; private String text;
@Enumerated(EnumType.STRING)
@Column(name = "flagged_status", nullable = false, columnDefinition = "varchar(255) default 'NORMAL'")
private FlaggedStatus flaggedStatus;
@Transient @Transient
private Map<String, Object> metadata; private Map<String, Object> metadata;
@ -154,6 +159,24 @@ public class Comment {
this.text = text; this.text = text;
} }
/**
* Gets the flagged status.
*
* @return the flagged status
*/
public FlaggedStatus getFlaggedStatus() {
return flaggedStatus;
}
/**
* Sets the flagged status.
*
* @param flaggedStatus the new flagged status
*/
public void setFlaggedStatus(FlaggedStatus flaggedStatus) {
this.flaggedStatus = flaggedStatus;
}
/** /**
* Gets the metadata. * Gets the metadata.
* *

View File

@ -22,7 +22,6 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
/** /**
* The Class Entry. * The Class Entry.
*/ */
@ -31,48 +30,32 @@ import com.google.common.collect.Maps;
@EntityListeners({ AuditingEntityListener.class }) @EntityListeners({ AuditingEntityListener.class })
public class Entry { public class Entry {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", nullable = false) @Column(name = "id", nullable = false)
private Long id; private Long id;
@Column(name = "author", nullable = false) @Column(name = "author", nullable = false)
private String author; private String author;
@Column(name = "created", nullable = false) @Column(name = "created", nullable = false)
private Instant created; private Instant created;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "entry_type", nullable = false) @Column(name = "entry_type", nullable = false)
private EntryType entryType; private EntryType entryType;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
@Column(name = "entry_status", nullable = false, columnDefinition = "varchar(255) default 'NORMAL'") @Column(name = "entry_status", nullable = false, columnDefinition = "varchar(255) default 'NORMAL'")
private EntryStatus entryStatus; private EntryStatus entryStatus;
@Enumerated(EnumType.STRING)
@Column(name = "flagged_status", nullable = false, columnDefinition = "varchar(255) default 'NORMAL'")
private FlaggedStatus flaggedStatus;
@Column(name = "url") @Column(name = "url")
private String url; private String url;
@Column(name = "title", nullable = false) @Column(name = "title", nullable = false)
private String title; private String title;
@Lob @Lob
@Column(name = "text") @Column(name = "text")
private String text; private String text;
@Transient @Transient
private Double ranking; private Double ranking;
@Transient @Transient
private Map<String, Object> metadata; private Map<String, Object> metadata;
@ -166,6 +149,24 @@ public class Entry {
this.entryStatus = entryStatus; this.entryStatus = entryStatus;
} }
/**
* Gets the flagged status.
*
* @return the flagged status
*/
public FlaggedStatus getFlaggedStatus() {
return flaggedStatus;
}
/**
* Sets the flagged status.
*
* @param flaggedStatus the new flagged status
*/
public void setFlaggedStatus(FlaggedStatus flaggedStatus) {
this.flaggedStatus = flaggedStatus;
}
/** /**
* Gets the url. * Gets the url.
* *

View File

@ -3,17 +3,11 @@
*/ */
package de.bstly.board.model; package de.bstly.board.model;
/** /**
* The Enum EntryStatus. * The Enum EntryStatus.
*/ */
public enum EntryStatus { public enum EntryStatus {
NORMAL, ARCHIVED
NORMAL,
ARCHIVED,
PINNED
} }

View File

@ -3,19 +3,11 @@
*/ */
package de.bstly.board.model; package de.bstly.board.model;
/** /**
* The Enum EntryType. * The Enum EntryType.
*/ */
public enum EntryType { public enum EntryType {
DISCUSSION, INTERN, LINK, QUESTION
DISCUSSION,
INTERN,
LINK,
QUESTION
} }

View File

@ -0,0 +1,107 @@
/**
*
*/
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;
/**
* The Class Flag.
*/
@Entity
@Table(name = "flags")
@EntityListeners({ AuditingEntityListener.class })
public class Flag {
@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;
/**
* Gets the id.
*
* @return the id
*/
public Long getId() {
return id;
}
/**
* Sets the id.
*
* @param id the new id
*/
public void setId(Long id) {
this.id = id;
}
/**
* Gets the target.
*
* @return the target
*/
public Long getTarget() {
return target;
}
/**
* Sets the target.
*
* @param target the new target
*/
public void setTarget(Long target) {
this.target = target;
}
/**
* Gets the target type.
*
* @return the target type
*/
public Types getTargetType() {
return targetType;
}
/**
* Sets the target type.
*
* @param targetType the new target type
*/
public void setTargetType(Types targetType) {
this.targetType = targetType;
}
/**
* Gets the author.
*
* @return the author
*/
public String getAuthor() {
return author;
}
/**
* Sets the author.
*
* @param author the new author
*/
public void setAuthor(String author) {
this.author = author;
}
}

View File

@ -0,0 +1,12 @@
/**
*
*/
package de.bstly.board.model;
/**
* The Enum FlaggedStatus.
*/
public enum FlaggedStatus {
NORMAL, FLAGGED, HIDDEN
}

View File

@ -23,7 +23,6 @@ import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.google.common.collect.Maps; import com.google.common.collect.Maps;
/** /**
* The Class LocalUser. * The Class LocalUser.
*/ */
@ -32,50 +31,31 @@ import com.google.common.collect.Maps;
@JsonInclude(Include.NON_EMPTY) @JsonInclude(Include.NON_EMPTY)
public class LocalUser { public class LocalUser {
@Id @Id
@Column(name = "username", nullable = false) @Column(name = "username", nullable = false)
private String username; private String username;
@Column(name = "external_id", nullable = true) @Column(name = "external_id", nullable = true)
private String externalId; private String externalId;
@JsonIgnore @JsonIgnore
@Column(name = "password_hash", nullable = true) @Column(name = "password_hash", nullable = true)
private String passwordHash; private String passwordHash;
@ElementCollection @ElementCollection
@LazyCollection(LazyCollectionOption.FALSE) @LazyCollection(LazyCollectionOption.FALSE)
@CollectionTable(name = "users_roles") @CollectionTable(name = "users_roles")
private List<String> roles; private List<String> roles;
@Lob @Lob
@Column(name = "about", nullable = true) @Column(name = "about", nullable = true)
private String about; private String about;
@Column(name = "email", nullable = true) @Column(name = "email", nullable = true)
private String email; private String email;
@Column(name = "locale", nullable = false, columnDefinition = "varchar(255) default 'en'") @Column(name = "locale", nullable = false, columnDefinition = "varchar(255) default 'en'")
private String locale; private String locale;
@Column(name = "dark_theme", columnDefinition = "boolean default false") @Column(name = "dark_theme", columnDefinition = "boolean default false")
private boolean darkTheme; private boolean darkTheme;
@ElementCollection @ElementCollection
@LazyCollection(LazyCollectionOption.FALSE) @LazyCollection(LazyCollectionOption.FALSE)
@CollectionTable(name = "users_settings") @CollectionTable(name = "users_settings")
private Map<String, String> settings; private Map<String, String> settings;
@Transient @Transient
private Map<String, Object> metadata; private Map<String, Object> metadata;

View File

@ -10,7 +10,6 @@ import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
/** /**
* The Class PersistentLogin. * The Class PersistentLogin.
*/ */
@ -18,20 +17,13 @@ import javax.persistence.Table;
@Table(name = "persistent_logins") @Table(name = "persistent_logins")
public class PersistentLogin { public class PersistentLogin {
@Column(name = "username", length = 64, nullable = false) @Column(name = "username", length = 64, nullable = false)
private String username; private String username;
@Id @Id
@Column(name = "series", length = 64) @Column(name = "series", length = 64)
private String series; private String series;
@Column(name = "token", length = 64, nullable = false) @Column(name = "token", length = 64, nullable = false)
private String token; private String token;
@Column(name = "last_used", nullable = false) @Column(name = "last_used", nullable = false)
private Instant last_used; private Instant last_used;

View File

@ -3,16 +3,10 @@
*/ */
package de.bstly.board.model; package de.bstly.board.model;
/** /**
* The Enum Types. * The Enum Types.
*/ */
public enum Types { public enum Types {
comment, entry, user
comment,
entry,
user
} }

View File

@ -21,26 +21,17 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@Table(name = "votes") @Table(name = "votes")
@EntityListeners({ AuditingEntityListener.class }) @EntityListeners({ AuditingEntityListener.class })
public class Vote { public class Vote {
@Id @Id
@GeneratedValue(strategy = GenerationType.AUTO) @GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id") @Column(name = "id")
private Long id; private Long id;
@Column(name = "target", nullable = false) @Column(name = "target", nullable = false)
private Long target; private Long target;
@Column(name = "target_type", nullable = false) @Column(name = "target_type", nullable = false)
private Types targetType; private Types targetType;
@Column(name = "author", nullable = false) @Column(name = "author", nullable = false)
private String author; private String author;
@Column(name = "type", nullable = false) @Column(name = "type", nullable = false)
private VoteType type; private VoteType type;

View File

@ -3,15 +3,10 @@
*/ */
package de.bstly.board.model; package de.bstly.board.model;
/** /**
* The Enum VoteType. * The Enum VoteType.
*/ */
public enum VoteType { public enum VoteType {
up, down
up,
down
} }

View File

@ -3,20 +3,12 @@
*/ */
package de.bstly.board.repository; 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.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import de.bstly.board.model.Comment; import de.bstly.board.model.Comment;
/** /**
* The Interface CommentRepository. * The Interface CommentRepository.
*/ */
@ -24,57 +16,57 @@ import de.bstly.board.model.Comment;
public interface CommentRepository public interface CommentRepository
extends JpaRepository<Comment, Long>, QuerydslPredicateExecutor<Comment> { extends JpaRepository<Comment, Long>, QuerydslPredicateExecutor<Comment> {
/** // /**
* Find all by ranking and parent. // * Find all by ranking and parent.
* // *
* @param target the target // * @param target the target
* @param date the date // * @param date the date
* @param gravity the gravity // * @param gravity the gravity
* @return the list // * @return the list
*/ // */
@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) // @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, // List<Comment> findAllByRankingAndParent(@Param("target") Long target,
@Param("date") Instant date, @Param("gravity") double gravity); // @Param("date") Instant date, @Param("gravity") double gravity);
//
/** // /**
* Find all by ranking and parent. // * Find all by ranking and parent.
* // *
* @param target the target // * @param target the target
* @param parent the parent // * @param parent the parent
* @param date the date // * @param date the date
* @param gravity the gravity // * @param gravity the gravity
* @return the list // * @return the list
*/ // */
@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) // @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, // List<Comment> findAllByRankingAndParent(@Param("target") Long target,
@Param("parent") Long parent, @Param("date") Instant date, // @Param("parent") Long parent, @Param("date") Instant date,
@Param("gravity") double gravity); // @Param("gravity") double gravity);
//
/** // /**
* Find all by ranking and parent. // * Find all by ranking and parent.
* // *
* @param target the target // * @param target the target
* @param date the date // * @param date the date
* @param gravity the gravity // * @param gravity the gravity
* @param pageable the pageable // * @param pageable the pageable
* @return the page // * @return the page
*/ // */
@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) // @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, // Page<Comment> findAllByRankingAndParent(@Param("target") Long target,
@Param("date") Instant date, @Param("gravity") double gravity, Pageable pageable); // @Param("date") Instant date, @Param("gravity") double gravity, Pageable pageable);
//
/** // /**
* Find all by ranking and parent. // * Find all by ranking and parent.
* // *
* @param target the target // * @param target the target
* @param parent the parent // * @param parent the parent
* @param date the date // * @param date the date
* @param gravity the gravity // * @param gravity the gravity
* @param pageable the pageable // * @param pageable the pageable
* @return the page // * @return the page
*/ // */
@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) // @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, // Page<Comment> findAllByRankingAndParent(@Param("target") Long target,
@Param("parent") Long parent, @Param("date") Instant date, // @Param("parent") Long parent, @Param("date") Instant date,
@Param("gravity") double gravity, Pageable pageable); // @Param("gravity") double gravity, Pageable pageable);
} }

View File

@ -27,33 +27,23 @@ public interface EntryRepository
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 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 COMMENTS_QUERY = "SELECT comment.target,MAX(comment.created) as last,COUNT(comment.id) AS count FROM comments as comment GROUP BY comment.target"; static final String COMMENTS_QUERY = "SELECT comment.target,MAX(comment.created) as last,COUNT(comment.id) AS count FROM comments as comment WHERE comment.flagged_status = 'NORMAL' GROUP BY comment.target";
static final String RANK_CALCULATION_QUERY = "SELECT entry.*, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) as points, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / IF(:gravity > 0, POW(TIMESTAMPDIFF(HOUR, entry.created, :before)+2,:gravity), 1) AS ranking FROM entries AS entry LEFT JOIN (" static final String RANK_CALCULATION_QUERY = "SELECT entry.*, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) as points, (IFNULL(upvote.count,0) - IFNULL(downvote.count,0)) / IF(:gravity > 0, POW(TIMESTAMPDIFF(HOUR, entry.created, :before)+2,:gravity), 1) AS ranking FROM entries AS entry LEFT JOIN ("
+ UPVOTES_QUERY + UPVOTES_QUERY
+ ") AS upvote ON upvote.target = entry.id LEFT JOIN (" + ") AS upvote ON upvote.target = entry.id LEFT JOIN ("
+ DOWNVOTES_QUERY + 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"; + ") AS downvote ON downvote.target = entry.id WHERE entry.created < :before AND entry.flagged_status = 'NORMAL' ORDER BY ranking DESC, entry.created DESC";
static final String COMMENT_CALCULATION_QUERY = "SELECT entry.*, IFNULL(comment.count,0) as comments, IFNULL(comment.count,0) / IF(:gravity > 0, POW(TIMESTAMPDIFF(HOUR, comment.last, :before)+2,:gravity), 1) AS ranking FROM entries AS entry LEFT JOIN (" static final String COMMENT_CALCULATION_QUERY = "SELECT entry.*, IFNULL(comment.count,0) as comments, IFNULL(comment.count,0) / IF(:gravity > 0, POW(TIMESTAMPDIFF(HOUR, comment.last, :before)+2,:gravity), 1) AS ranking FROM entries AS entry LEFT JOIN ("
+ COMMENTS_QUERY + COMMENTS_QUERY
+ ") AS comment ON comment.target = entry.id WHERE entry.created < :before AND entry.entry_status = 'NORMAL' ORDER BY ranking DESC, entry.created DESC"; + ") AS comment ON comment.target = entry.id WHERE entry.created < :before AND entry.flagged_status = 'NORMAL' ORDER BY ranking DESC, entry.created DESC";
static final String LAST_COMMENT_QUERY = "SELECT entry.* FROM entries AS entry LEFT JOIN (" static final String LAST_COMMENT_QUERY = "SELECT entry.* FROM entries AS entry LEFT JOIN ("
+ COMMENTS_QUERY + COMMENTS_QUERY
+ ") AS comment ON comment.target = entry.id WHERE entry.created < :before AND entry.entry_status = 'NORMAL' ORDER BY comment.last DESC, entry.created DESC"; + ") AS comment ON comment.target = entry.id WHERE entry.created < :before AND entry.flagged_status = 'NORMAL' ORDER BY comment.last 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 (" static final String COUNT_QUERY = "SELECT count(*) FROM entries as entry WHERE entry.created < :before AND entry.flagged_status = 'NORMAL'";
+ 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 ("
+ RANK_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";
/** /**
* Find all by ranking. * Find all by ranking.
@ -89,16 +79,4 @@ public interface EntryRepository
@Query(value = LAST_COMMENT_QUERY, countQuery = COUNT_QUERY, nativeQuery = true) @Query(value = LAST_COMMENT_QUERY, countQuery = COUNT_QUERY, nativeQuery = true)
Page<Entry> findAllByLastComment(@Param("before") Instant before, Pageable pageable); Page<Entry> findAllByLastComment(@Param("before") Instant before, Pageable pageable);
/**
* Find all by ranking archive.
*
* @param before the before
* @param gravity the gravity
* @param pageable the pageable
* @return the page
*/
@Query(value = ARCHIVE_CALCULATION_QUERY, countQuery = COUNT_QUERY, nativeQuery = true)
Page<RankedEntry> findAllByRankingArchive(@Param("before") Instant before,
@Param("gravity") double gravity, Pageable pageable);
} }

View File

@ -0,0 +1,18 @@
/**
*
*/
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.Flag;
/**
* The Interface FlagRepository.
*/
@Repository
public interface FlagRepository extends JpaRepository<Flag, Long>, QuerydslPredicateExecutor<Flag> {
}

View File

@ -1,46 +0,0 @@
# 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.