initial commit

This commit is contained in:
2021-10-03 17:17:00 +02:00
commit 3fffe798cd
45 changed files with 3846 additions and 0 deletions
@@ -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;
}
}
}
@@ -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());
}
}
@@ -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;
}
}
@@ -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() + "'");
}
}
}
@@ -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;
}
}
@@ -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;
}
}
@@ -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");
}
}
}