From 08301d8d34c6942a5b04f741aef4dfd7e92ec72b Mon Sep 17 00:00:00 2001 From: Alexander von Trostorff Date: Tue, 10 Apr 2018 23:54:31 +0200 Subject: [PATCH] Implement voting Fixes #200 --- .../com/faforever/api/data/domain/Player.java | 8 ++ .../com/faforever/api/data/domain/Vote.java | 44 ++++++++ .../api/data/domain/VotingAnswer.java | 42 +++++++ .../api/data/domain/VotingChoice.java | 75 +++++++++++++ .../api/data/domain/VotingQuestion.java | 79 ++++++++++++++ .../api/data/domain/VotingSubject.java | 103 ++++++++++++++++++ .../data/listeners/VotingChoiceEnricher.java | 15 +++ .../listeners/VotingQuestionEnricher.java | 18 +++ .../data/listeners/VotingSubjectEnricher.java | 15 +++ .../com/faforever/api/error/ErrorCode.java | 5 +- .../api/game/GamePlayerStatsRepository.java | 9 ++ .../faforever/api/security/OAuthScope.java | 5 +- .../faforever/api/voting/VoteRepository.java | 15 +++ .../api/voting/VotingAnswerRepository.java | 7 ++ .../api/voting/VotingChoiceRepository.java | 7 ++ .../api/voting/VotingController.java | 36 ++++++ .../faforever/api/voting/VotingService.java | 65 +++++++++++ .../api/voting/VotingSubjectRepository.java | 7 ++ 18 files changed, 553 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/faforever/api/data/domain/Vote.java create mode 100644 src/main/java/com/faforever/api/data/domain/VotingAnswer.java create mode 100644 src/main/java/com/faforever/api/data/domain/VotingChoice.java create mode 100644 src/main/java/com/faforever/api/data/domain/VotingQuestion.java create mode 100644 src/main/java/com/faforever/api/data/domain/VotingSubject.java create mode 100644 src/main/java/com/faforever/api/data/listeners/VotingChoiceEnricher.java create mode 100644 src/main/java/com/faforever/api/data/listeners/VotingQuestionEnricher.java create mode 100644 src/main/java/com/faforever/api/data/listeners/VotingSubjectEnricher.java create mode 100644 src/main/java/com/faforever/api/game/GamePlayerStatsRepository.java create mode 100644 src/main/java/com/faforever/api/voting/VoteRepository.java create mode 100644 src/main/java/com/faforever/api/voting/VotingAnswerRepository.java create mode 100644 src/main/java/com/faforever/api/voting/VotingChoiceRepository.java create mode 100644 src/main/java/com/faforever/api/voting/VotingController.java create mode 100644 src/main/java/com/faforever/api/voting/VotingService.java create mode 100644 src/main/java/com/faforever/api/voting/VotingSubjectRepository.java diff --git a/src/main/java/com/faforever/api/data/domain/Player.java b/src/main/java/com/faforever/api/data/domain/Player.java index bab0ebb12..a35cae53e 100644 --- a/src/main/java/com/faforever/api/data/domain/Player.java +++ b/src/main/java/com/faforever/api/data/domain/Player.java @@ -7,6 +7,7 @@ import lombok.Setter; import org.hibernate.annotations.BatchSize; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.OneToMany; @@ -30,6 +31,7 @@ public class Player extends Login { private List clanMemberships; private List names; private List avatarAssignments; + private List votes; @OneToOne(mappedBy = "player", fetch = FetchType.LAZY) @BatchSize(size = 1000) @@ -78,4 +80,10 @@ public List getAvatarAssignments() { public String toString() { return "Player(" + getId() + ", " + getLogin() + ")"; } + + @Transient + @OneToMany(mappedBy = "player", cascade = CascadeType.ALL, orphanRemoval = true) + public List getVotes() { + return votes; + } } diff --git a/src/main/java/com/faforever/api/data/domain/Vote.java b/src/main/java/com/faforever/api/data/domain/Vote.java new file mode 100644 index 000000000..df47a42da --- /dev/null +++ b/src/main/java/com/faforever/api/data/domain/Vote.java @@ -0,0 +1,44 @@ +package com.faforever.api.data.domain; + +import com.yahoo.elide.annotation.Include; +import com.yahoo.elide.annotation.ReadPermission; +import lombok.Setter; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import java.util.List; + +@Entity +@Table(name = "vote") +@Include(type = Vote.TYPE_NAME) +@ReadPermission(expression = "Prefab.Role.None") +@Setter +public class Vote extends AbstractEntity { + public static final String TYPE_NAME = "vote"; + + private Player player; + private VotingSubject votingSubject; + private List votingAnswers; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "player_id") + public Player getPlayer() { + return player; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "voting_subject_id") + public VotingSubject getVotingSubject() { + return votingSubject; + } + + @OneToMany(mappedBy = "vote", cascade = CascadeType.ALL, orphanRemoval = true) + public List getVotingAnswers() { + return votingAnswers; + } +} diff --git a/src/main/java/com/faforever/api/data/domain/VotingAnswer.java b/src/main/java/com/faforever/api/data/domain/VotingAnswer.java new file mode 100644 index 000000000..60d4ef6a9 --- /dev/null +++ b/src/main/java/com/faforever/api/data/domain/VotingAnswer.java @@ -0,0 +1,42 @@ +package com.faforever.api.data.domain; + +import com.yahoo.elide.annotation.Include; +import com.yahoo.elide.annotation.ReadPermission; +import lombok.Setter; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name = "voting_answer") +@Include(type = VotingAnswer.TYPE_NAME) +@ReadPermission(expression = "Prefab.Role.None") +@Setter +public class VotingAnswer extends AbstractEntity { + public static final String TYPE_NAME = "votingAnswer"; + + private VotingQuestion votingQuestion; + private Vote vote; + private VotingChoice votingChoice; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "voting_question_id") + public VotingQuestion getVotingQuestion() { + return votingQuestion; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "vote_id") + public Vote getVote() { + return vote; + } + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "voting_choice_id") + public VotingChoice getVotingChoice() { + return votingChoice; + } +} diff --git a/src/main/java/com/faforever/api/data/domain/VotingChoice.java b/src/main/java/com/faforever/api/data/domain/VotingChoice.java new file mode 100644 index 000000000..df504aad2 --- /dev/null +++ b/src/main/java/com/faforever/api/data/domain/VotingChoice.java @@ -0,0 +1,75 @@ +package com.faforever.api.data.domain; + +import com.faforever.api.data.checks.permission.IsModerator; +import com.faforever.api.data.listeners.VotingChoiceEnricher; +import com.yahoo.elide.annotation.ComputedAttribute; +import com.yahoo.elide.annotation.CreatePermission; +import com.yahoo.elide.annotation.DeletePermission; +import com.yahoo.elide.annotation.Exclude; +import com.yahoo.elide.annotation.Include; +import com.yahoo.elide.annotation.ReadPermission; +import com.yahoo.elide.annotation.SharePermission; +import com.yahoo.elide.annotation.UpdatePermission; +import lombok.Setter; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Entity +@Table(name = "voting_choice") +@ReadPermission(expression = "Prefab.Role.All") +@SharePermission(expression = IsModerator.EXPRESSION) +@DeletePermission(expression = IsModerator.EXPRESSION) +@UpdatePermission(expression = IsModerator.EXPRESSION) +@CreatePermission(expression = IsModerator.EXPRESSION) +@Include(rootLevel = true, type = VotingChoice.TYPE_NAME) +@Setter +@EntityListeners(VotingChoiceEnricher.class) +public class VotingChoice extends AbstractEntity { + public static final String TYPE_NAME = "votingChoice"; + + private String name; + private int numberOfAnswers; + private String description; + private VotingQuestion votingQuestion; + private List votingAnswers; + + @NotNull + @Column(name = "name", nullable = false) + public String getName() { + return name; + } + + @Transient + @ComputedAttribute + public int getNumberOfAnswers() { + return numberOfAnswers; + } + + @Column(name = "description") + public String getDescription() { + return description; + } + + @JoinColumn(name = "voting_question_id") + @ManyToOne(fetch = FetchType.LAZY) + public VotingQuestion getVotingQuestion() { + return votingQuestion; + } + + @Exclude + @OneToMany(mappedBy = "votingChoice", cascade = CascadeType.ALL, orphanRemoval = true) + public List getVotingAnswers() { + return votingAnswers; + } +} diff --git a/src/main/java/com/faforever/api/data/domain/VotingQuestion.java b/src/main/java/com/faforever/api/data/domain/VotingQuestion.java new file mode 100644 index 000000000..454ed099b --- /dev/null +++ b/src/main/java/com/faforever/api/data/domain/VotingQuestion.java @@ -0,0 +1,79 @@ +package com.faforever.api.data.domain; + +import com.faforever.api.data.checks.permission.IsModerator; +import com.faforever.api.data.listeners.VotingQuestionEnricher; +import com.yahoo.elide.annotation.ComputedAttribute; +import com.yahoo.elide.annotation.CreatePermission; +import com.yahoo.elide.annotation.DeletePermission; +import com.yahoo.elide.annotation.Include; +import com.yahoo.elide.annotation.ReadPermission; +import com.yahoo.elide.annotation.SharePermission; +import com.yahoo.elide.annotation.UpdatePermission; +import lombok.Setter; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.FetchType; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; +import java.util.List; + +@Entity +@Table(name = "voting_question") +@ReadPermission(expression = "Prefab.Role.All") +@SharePermission(expression = IsModerator.EXPRESSION) +@DeletePermission(expression = IsModerator.EXPRESSION) +@UpdatePermission(expression = IsModerator.EXPRESSION) +@CreatePermission(expression = IsModerator.EXPRESSION) +@Include(rootLevel = true, type = VotingQuestion.TYPE_NAME) +@Setter +@EntityListeners(VotingQuestionEnricher.class) +public class VotingQuestion extends AbstractEntity { + public static final String TYPE_NAME = "votingQuestion"; + + private int numberOfAnswers; + private String question; + private String description; + private int maxAnswers; + private VotingSubject votingSubject; + private List votingChoices; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "voting_subject_id") + public VotingSubject getVotingSubject() { + return votingSubject; + } + + @NotNull + @Column(name = "question", nullable = false) + public String getQuestion() { + return question; + } + + @Column(name = "description") + public String getDescription() { + return description; + } + + @Column(name = "max_answers") + public int getMaxAnswers() { + return maxAnswers; + } + + @Transient + @ComputedAttribute + public int getNumberOfAnswers() { + return numberOfAnswers; + } + + @OneToMany(mappedBy = "votingQuestion", cascade = CascadeType.ALL, orphanRemoval = true) + public List getVotingChoices() { + return votingChoices; + } +} diff --git a/src/main/java/com/faforever/api/data/domain/VotingSubject.java b/src/main/java/com/faforever/api/data/domain/VotingSubject.java new file mode 100644 index 000000000..dc8f5ef8a --- /dev/null +++ b/src/main/java/com/faforever/api/data/domain/VotingSubject.java @@ -0,0 +1,103 @@ +package com.faforever.api.data.domain; + +import com.faforever.api.data.checks.permission.IsModerator; +import com.faforever.api.data.listeners.VotingSubjectEnricher; +import com.yahoo.elide.annotation.ComputedAttribute; +import com.yahoo.elide.annotation.CreatePermission; +import com.yahoo.elide.annotation.DeletePermission; +import com.yahoo.elide.annotation.Exclude; +import com.yahoo.elide.annotation.Include; +import com.yahoo.elide.annotation.ReadPermission; +import com.yahoo.elide.annotation.SharePermission; +import com.yahoo.elide.annotation.UpdatePermission; +import lombok.Setter; +import org.hibernate.validator.constraints.NotEmpty; + +import javax.persistence.CascadeType; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EntityListeners; +import javax.persistence.OneToMany; +import javax.persistence.Table; +import javax.persistence.Transient; +import javax.validation.constraints.DecimalMin; +import javax.validation.constraints.NotNull; +import java.time.OffsetDateTime; +import java.util.List; + +@Entity +@Table(name = "voting_subject") +@Include(rootLevel = true, type = VotingSubject.TYPE_NAME) +@ReadPermission(expression = "Prefab.Role.All") +@SharePermission(expression = IsModerator.EXPRESSION) +@DeletePermission(expression = IsModerator.EXPRESSION) +@UpdatePermission(expression = IsModerator.EXPRESSION) +@CreatePermission(expression = IsModerator.EXPRESSION) +@Setter +@EntityListeners(VotingSubjectEnricher.class) +public class VotingSubject extends AbstractEntity { + public static final String TYPE_NAME = "votingSubject"; + + private String name; + private int numberOfVotes; + private String topicUrl; + private OffsetDateTime beginOfVoteTime; + private OffsetDateTime endOfVoteTime; + private int minGamesToVote; + private String description; + private List votes; + private List votingQuestions; + + @Column(name = "name") + @NotNull + public String getName() { + return name; + } + + @Transient + @ComputedAttribute + public int getNumberOfVotes() { + return numberOfVotes; + } + + + @NotNull + @Column(name = "begin_of_vote_time") + public OffsetDateTime getBeginOfVoteTime() { + return beginOfVoteTime; + } + + @NotNull + @Column(name = "end_of_vote_time") + public OffsetDateTime getEndOfVoteTime() { + return endOfVoteTime; + } + + @DecimalMin("0") + @Column(name = "min_games_to_vote") + public int getMinGamesToVote() { + return minGamesToVote; + } + + @Column(name = "description") + public String getDescription() { + return description; + } + + @Column(name = "topic_url") + public String getTopicUrl() { + return topicUrl; + } + + @Exclude + @OneToMany(mappedBy = "votingSubject", cascade = CascadeType.ALL, orphanRemoval = true) + public List getVotes() { + return votes; + } + + @NotEmpty(message = "A subject needs at least one Question") + @OneToMany(mappedBy = "votingSubject", cascade = CascadeType.ALL, orphanRemoval = true) + public List getVotingQuestions() { + return votingQuestions; + } +} diff --git a/src/main/java/com/faforever/api/data/listeners/VotingChoiceEnricher.java b/src/main/java/com/faforever/api/data/listeners/VotingChoiceEnricher.java new file mode 100644 index 000000000..2a69dc23e --- /dev/null +++ b/src/main/java/com/faforever/api/data/listeners/VotingChoiceEnricher.java @@ -0,0 +1,15 @@ +package com.faforever.api.data.listeners; + +import com.faforever.api.data.domain.VotingChoice; +import org.springframework.stereotype.Component; + +import javax.persistence.PostLoad; + +@Component +public class VotingChoiceEnricher { + + @PostLoad + public void enhance(VotingChoice votingChoice) { + votingChoice.setNumberOfAnswers(votingChoice.getVotingAnswers().size()); + } +} diff --git a/src/main/java/com/faforever/api/data/listeners/VotingQuestionEnricher.java b/src/main/java/com/faforever/api/data/listeners/VotingQuestionEnricher.java new file mode 100644 index 000000000..70462defd --- /dev/null +++ b/src/main/java/com/faforever/api/data/listeners/VotingQuestionEnricher.java @@ -0,0 +1,18 @@ +package com.faforever.api.data.listeners; + +import com.faforever.api.data.domain.VotingQuestion; +import org.springframework.stereotype.Component; + +import javax.persistence.PostLoad; + +@Component +public class VotingQuestionEnricher { + + @PostLoad + public void enhance(VotingQuestion votingQuestion) { + long numberOfAnswers = votingQuestion.getVotingChoices().stream() + .map(votingChoice -> votingChoice.getVotingAnswers().stream()) + .count(); + votingQuestion.setNumberOfAnswers((int) numberOfAnswers); + } +} diff --git a/src/main/java/com/faforever/api/data/listeners/VotingSubjectEnricher.java b/src/main/java/com/faforever/api/data/listeners/VotingSubjectEnricher.java new file mode 100644 index 000000000..8041bd19b --- /dev/null +++ b/src/main/java/com/faforever/api/data/listeners/VotingSubjectEnricher.java @@ -0,0 +1,15 @@ +package com.faforever.api.data.listeners; + +import com.faforever.api.data.domain.VotingSubject; +import org.springframework.stereotype.Component; + +import javax.persistence.PostLoad; + +@Component +public class VotingSubjectEnricher { + + @PostLoad + public void enhance(VotingSubject votingSubject) { + votingSubject.setNumberOfVotes(votingSubject.getVotes().size()); + } +} diff --git a/src/main/java/com/faforever/api/error/ErrorCode.java b/src/main/java/com/faforever/api/error/ErrorCode.java index 40c0fc849..c05899d1f 100644 --- a/src/main/java/com/faforever/api/error/ErrorCode.java +++ b/src/main/java/com/faforever/api/error/ErrorCode.java @@ -78,7 +78,10 @@ public enum ErrorCode { AVATAR_NAME_CONFLICT(168, "Invalid avatar file name", "Avatar file name ''{0}'' already exists."), AVATAR_IN_USE(169, "Avatar in use", "Could not delete avatar {0, number}. Avatar still in use."), ENTITY_NOT_FOUND(170, "Entity not found", "Entity with id: {0} not found."), - INVALID_AVATAR_DIMENSION(171, "Invalid avatar dimensions", "Avatar dimensions must be {0, number}x{1, number}, was: {2, number}x{3, number}."); + INVALID_AVATAR_DIMENSION(171, "Invalid avatar dimensions", "Avatar dimensions must be {0, number}x{1, number}, was: {2, number}x{3, number}."), + VOTED_TWICE(172, "You can not vote twice", "There was a vote found for your user and this subject"), + NOT_ENOUGH_GAMES(173, "You have not got enough games to vote", "You have '{0}' games but you need '{1}'"), + TOO_MANY_ANSWERS(174, "You have to much answers selected in question", "You selected '{0}' but you can only select '{1}'"); private final int code; private final String title; diff --git a/src/main/java/com/faforever/api/game/GamePlayerStatsRepository.java b/src/main/java/com/faforever/api/game/GamePlayerStatsRepository.java new file mode 100644 index 000000000..fd9bd23ef --- /dev/null +++ b/src/main/java/com/faforever/api/game/GamePlayerStatsRepository.java @@ -0,0 +1,9 @@ +package com.faforever.api.game; + +import com.faforever.api.data.domain.GamePlayerStats; +import com.faforever.api.data.domain.Player; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface GamePlayerStatsRepository extends JpaRepository { + int countByPlayer(Player player); +} diff --git a/src/main/java/com/faforever/api/security/OAuthScope.java b/src/main/java/com/faforever/api/security/OAuthScope.java index 25f0325be..ea944479e 100644 --- a/src/main/java/com/faforever/api/security/OAuthScope.java +++ b/src/main/java/com/faforever/api/security/OAuthScope.java @@ -15,7 +15,9 @@ public enum OAuthScope { UPLOAD_MOD(OAuthScope._UPLOAD_MOD, "Upload mods"), UPLOAD_AVATAR(OAuthScope._UPLOAD_AVATAR, "Upload avatars"), WRITE_ACCOUNT_DATA(OAuthScope._WRITE_ACCOUNT_DATA, "Edit account data"), - EDIT_CLAN_DATA(OAuthScope._EDIT_CLAN_DATA, "Edit clan data"); + EDIT_CLAN_DATA(OAuthScope._EDIT_CLAN_DATA, "Edit clan data"), + VOTE(OAuthScope._Vote, "Vote"); + public static final String _PUBLIC_PROFILE = "public_profile"; public static final String _READ_ACHIEVEMENTS = "read_achievements"; @@ -27,6 +29,7 @@ public enum OAuthScope { public static final String _UPLOAD_AVATAR = "upload_avatar"; public static final String _WRITE_ACCOUNT_DATA = "write_account_data"; public static final String _EDIT_CLAN_DATA = "edit_clan_data"; + public static final String _Vote = "vote"; private static final Map fromString; diff --git a/src/main/java/com/faforever/api/voting/VoteRepository.java b/src/main/java/com/faforever/api/voting/VoteRepository.java new file mode 100644 index 000000000..89beea769 --- /dev/null +++ b/src/main/java/com/faforever/api/voting/VoteRepository.java @@ -0,0 +1,15 @@ +package com.faforever.api.voting; + +import com.faforever.api.data.domain.Player; +import com.faforever.api.data.domain.Vote; +import com.faforever.api.data.domain.VotingSubject; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface VoteRepository extends JpaRepository { + Optional findByPlayerAndVotingSubject(Player player, VotingSubject votingSubject); + + List findByVotingSubject(VotingSubject votingSubject); +} diff --git a/src/main/java/com/faforever/api/voting/VotingAnswerRepository.java b/src/main/java/com/faforever/api/voting/VotingAnswerRepository.java new file mode 100644 index 000000000..aa1d9b139 --- /dev/null +++ b/src/main/java/com/faforever/api/voting/VotingAnswerRepository.java @@ -0,0 +1,7 @@ +package com.faforever.api.voting; + +import com.faforever.api.data.domain.VotingAnswer; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VotingAnswerRepository extends JpaRepository { +} diff --git a/src/main/java/com/faforever/api/voting/VotingChoiceRepository.java b/src/main/java/com/faforever/api/voting/VotingChoiceRepository.java new file mode 100644 index 000000000..4b7344e5e --- /dev/null +++ b/src/main/java/com/faforever/api/voting/VotingChoiceRepository.java @@ -0,0 +1,7 @@ +package com.faforever.api.voting; + +import com.faforever.api.data.domain.VotingChoice; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VotingChoiceRepository extends JpaRepository { +} diff --git a/src/main/java/com/faforever/api/voting/VotingController.java b/src/main/java/com/faforever/api/voting/VotingController.java new file mode 100644 index 000000000..06870ec53 --- /dev/null +++ b/src/main/java/com/faforever/api/voting/VotingController.java @@ -0,0 +1,36 @@ +package com.faforever.api.voting; + +import com.faforever.api.data.domain.Vote; +import com.faforever.api.player.PlayerService; +import com.faforever.api.security.OAuthScope; +import io.swagger.annotations.ApiOperation; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.inject.Inject; + +@RestController +@RequestMapping(path = VotingController.PATH) +public class VotingController { + static final String PATH = "/vote"; + private static final String JSON_API_MEDIA_TYPE = "application/vnd.api+json"; + private final VotingService votingService; + private final PlayerService playerService; + + @Inject + public VotingController(VotingService votingService, PlayerService playerService) { + this.votingService = votingService; + this.playerService = playerService; + } + + @ApiOperation(value = "Post a vote") + @PreAuthorize("#oauth2.hasScope('" + OAuthScope._Vote + "')") + @RequestMapping(method = RequestMethod.POST, produces = JSON_API_MEDIA_TYPE) + public void post(@RequestBody Vote vote, Authentication authentication) { + votingService.saveVote(vote, playerService.getPlayer(authentication)); + } +} diff --git a/src/main/java/com/faforever/api/voting/VotingService.java b/src/main/java/com/faforever/api/voting/VotingService.java new file mode 100644 index 000000000..bd642bed4 --- /dev/null +++ b/src/main/java/com/faforever/api/voting/VotingService.java @@ -0,0 +1,65 @@ +package com.faforever.api.voting; + +import com.faforever.api.data.domain.Player; +import com.faforever.api.data.domain.Vote; +import com.faforever.api.data.domain.VotingChoice; +import com.faforever.api.data.domain.VotingSubject; +import com.faforever.api.error.ApiException; +import com.faforever.api.error.Error; +import com.faforever.api.error.ErrorCode; +import com.faforever.api.game.GamePlayerStatsRepository; +import org.springframework.stereotype.Service; +import org.springframework.util.Assert; + +import java.util.Collections; +import java.util.Optional; + +@Service +public class VotingService { + private final VoteRepository voteRepository; + private final VotingAnswerRepository votingAnswerRepository; + private final VotingSubjectRepository votingSubjectRepository; + private final GamePlayerStatsRepository gamePlayerStatsRepository; + private final VotingChoiceRepository votingChoiceRepository; + + public VotingService(VoteRepository voteRepository, VotingAnswerRepository votingAnswerRepository, VotingSubjectRepository votingSubjectRepository, GamePlayerStatsRepository gamePlayerStatsRepository, VotingChoiceRepository votingChoiceRepository) { + this.voteRepository = voteRepository; + this.votingAnswerRepository = votingAnswerRepository; + this.votingSubjectRepository = votingSubjectRepository; + this.gamePlayerStatsRepository = gamePlayerStatsRepository; + this.votingChoiceRepository = votingChoiceRepository; + } + + void saveVote(Vote vote, Player player) { + vote.setPlayer(player); + Assert.notNull(vote.getVotingSubject(), "You must specify a subject"); + Optional byPlayerAndVotingSubject = voteRepository.findByPlayerAndVotingSubject(player, vote.getVotingSubject()); + if (byPlayerAndVotingSubject.isPresent()) { + throw new ApiException(new Error(ErrorCode.VOTED_TWICE)); + } + VotingSubject subject = votingSubjectRepository.findOne(vote.getVotingSubject().getId()); + int gamesPlayed = gamePlayerStatsRepository.countByPlayer(player); + //TODO: count only valid games + if (gamesPlayed < subject.getMinGamesToVote()) { + throw new ApiException(new Error(ErrorCode.NOT_ENOUGH_GAMES, gamesPlayed, subject.getMinGamesToVote())); + } + + if (vote.getVotingAnswers() == null) { + vote.setVotingAnswers(Collections.emptyList()); + } + + vote.getVotingAnswers().forEach(votingAnswer -> { + VotingChoice choice = votingChoiceRepository.findOne(votingAnswer.getVotingChoice().getId()); + long count = vote.getVotingAnswers().stream() + .filter(votingAnswer1 -> votingAnswer1.getVotingQuestion().equals(votingAnswer.getVotingQuestion())) + .count(); + int maxAnswers = choice.getVotingQuestion().getMaxAnswers(); + if (maxAnswers > count) { + throw new ApiException(new Error(ErrorCode.TOO_MANY_ANSWERS, count, maxAnswers)); + } + }); + + votingAnswerRepository.save(vote.getVotingAnswers()); + voteRepository.save(vote); + } +} diff --git a/src/main/java/com/faforever/api/voting/VotingSubjectRepository.java b/src/main/java/com/faforever/api/voting/VotingSubjectRepository.java new file mode 100644 index 000000000..f75f887d0 --- /dev/null +++ b/src/main/java/com/faforever/api/voting/VotingSubjectRepository.java @@ -0,0 +1,7 @@ +package com.faforever.api.voting; + +import com.faforever.api.data.domain.VotingSubject; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface VotingSubjectRepository extends JpaRepository { +}