Skip to content

Commit

Permalink
Merge pull request #200 from Arquisoft/develop
Browse files Browse the repository at this point in the history
v1.2.0
  • Loading branch information
UO283615 authored Apr 8, 2024
2 parents ca99726 + bcf6b64 commit 227ce1e
Show file tree
Hide file tree
Showing 84 changed files with 1,279 additions and 429 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ jobs:
node-version: 20
- run: npm --prefix webapp install
- run: npm --prefix webapp run build
#- run: npm --prefix webapp run test:e2e TODO: re-enable
- run: npm --prefix webapp run test:e2e
docker-push-api:
runs-on: ubuntu-latest
needs: [ e2e-tests ]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
@Log4j2
@Order(Ordered.HIGHEST_PRECEDENCE)
public class CustomControllerAdvice extends ResponseEntityExceptionHandler {
@ExceptionHandler(InternalApiErrorException.class)
public ResponseEntity<String> handleInternalApiErrorException(InternalApiErrorException exception){
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.SERVICE_UNAVAILABLE);
}
@ExceptionHandler(InvalidAuthenticationException.class)
public ResponseEntity<String> handleInvalidAuthenticationException(InvalidAuthenticationException exception){
log.error(exception.getMessage(),exception);
Expand All @@ -28,7 +33,11 @@ public ResponseEntity<String> handleNoSuchElementException(NoSuchElementExceptio
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.NOT_FOUND);
}

@ExceptionHandler(IllegalStateException.class)
public ResponseEntity<String> handleIllegalStateException(IllegalStateException exception){
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.CONFLICT);
}
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException exception){
log.error(exception.getMessage(),exception);
Expand Down Expand Up @@ -60,7 +69,7 @@ public ResponseEntity<String> handleInternalAuthenticationServiceException(Inter
@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception exception){
log.error(exception.getMessage(),exception);
return new ResponseEntity<>(exception.getMessage(),HttpStatus.INTERNAL_SERVER_ERROR);
return new ResponseEntity<>("Internal Server Error",HttpStatus.INTERNAL_SERVER_ERROR);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lab.en2b.quizapi.commons.exceptions;

public class InternalApiErrorException extends RuntimeException{
public InternalApiErrorException(String message) {
super(message);
}
}
24 changes: 17 additions & 7 deletions api/src/main/java/lab/en2b/quizapi/game/Game.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import lab.en2b.quizapi.commons.user.User;
import lab.en2b.quizapi.questions.answer.Answer;
import lab.en2b.quizapi.questions.question.Question;
import lab.en2b.quizapi.questions.question.QuestionRepository;
import lombok.*;

import java.time.LocalDateTime;
Expand All @@ -25,10 +24,10 @@ public class Game {
@Setter(AccessLevel.NONE)
private Long id;

private int rounds = 9;
private int actualRound = 0;
private Long rounds = 9L;
private Long actualRound = 0L;

private int correctlyAnsweredQuestions = 0;
private Long correctlyAnsweredQuestions = 0L;
private String language;
private LocalDateTime roundStartTime;
@NonNull
Expand All @@ -47,6 +46,8 @@ public class Game {
inverseJoinColumns=
@JoinColumn(name="question_id", referencedColumnName="id")
)

@OrderColumn
private List<Question> questions;
private boolean isGameOver;

Expand All @@ -69,10 +70,14 @@ private void increaseRound(){
}

public boolean isGameOver(){
return getActualRound() > getRounds();
return isGameOver && getActualRound() >= getRounds();
}


public Question getCurrentQuestion() {
if(getRoundStartTime() == null){
throw new IllegalStateException("The round is not active!");
}
if(currentRoundIsOver())
throw new IllegalStateException("The current round is over!");
if(isGameOver())
Expand All @@ -85,10 +90,10 @@ private boolean currentRoundIsOver(){
}

private boolean roundTimeHasExpired(){
return LocalDateTime.now().isAfter(getRoundStartTime().plusSeconds(getRoundDuration()));
return getRoundStartTime()!= null && LocalDateTime.now().isAfter(getRoundStartTime().plusSeconds(getRoundDuration()));
}

public void answerQuestion(Long answerId, QuestionRepository questionRepository){
public boolean answerQuestion(Long answerId){
if(currentRoundIsOver())
throw new IllegalStateException("You can't answer a question when the current round is over!");
if (isGameOver())
Expand All @@ -100,6 +105,7 @@ public void answerQuestion(Long answerId, QuestionRepository questionRepository)
setCorrectlyAnsweredQuestions(getCorrectlyAnsweredQuestions() + 1);
}
setCurrentQuestionAnswered(true);
return q.isCorrectAnswer(answerId);
}
public void setLanguage(String language){
if(!isLanguageSupported(language))
Expand All @@ -110,4 +116,8 @@ public void setLanguage(String language){
private boolean isLanguageSupported(String language) {
return language.equals("en") || language.equals("es");
}

public boolean shouldBeGameOver() {
return getActualRound() >= getRounds() && !isGameOver;
}
}
3 changes: 2 additions & 1 deletion api/src/main/java/lab/en2b/quizapi/game/GameController.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto;
import lab.en2b.quizapi.game.dtos.GameAnswerDto;
import lab.en2b.quizapi.game.dtos.GameResponseDto;
import lab.en2b.quizapi.questions.question.QuestionCategory;
Expand Down Expand Up @@ -57,7 +58,7 @@ public ResponseEntity<QuestionResponseDto> getCurrentQuestion(@PathVariable Long
@ApiResponse(responseCode = "403", description = "You are not logged in", content = @io.swagger.v3.oas.annotations.media.Content),
})
@PostMapping("/{id}/answer")
public ResponseEntity<GameResponseDto> answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){
public ResponseEntity<AnswerGameResponseDto> answerQuestion(@PathVariable Long id, @RequestBody GameAnswerDto dto, Authentication authentication){
return ResponseEntity.ok(gameService.answerQuestion(id, dto, authentication));
}

Expand Down
83 changes: 62 additions & 21 deletions api/src/main/java/lab/en2b/quizapi/game/GameService.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lab.en2b.quizapi.game;

import lab.en2b.quizapi.commons.user.UserService;
import lab.en2b.quizapi.game.dtos.AnswerGameResponseDto;
import lab.en2b.quizapi.game.dtos.GameAnswerDto;
import lab.en2b.quizapi.game.dtos.GameResponseDto;
import lab.en2b.quizapi.game.mappers.GameResponseDtoMapper;
Expand All @@ -14,10 +15,12 @@
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

@Service
@RequiredArgsConstructor
Expand All @@ -29,35 +32,40 @@ public class GameService {
private final QuestionRepository questionRepository;
private final QuestionResponseDtoMapper questionResponseDtoMapper;
private final StatisticsRepository statisticsRepository;

@Transactional
public GameResponseDto newGame(Authentication authentication) {
if (gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()).isPresent()){
return gameResponseDtoMapper.apply(gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId()).get());
Optional<Game> game = gameRepository.findActiveGameForUser(userService.getUserByAuthentication(authentication).getId());

if (game.isPresent()){
if (game.get().shouldBeGameOver()){
game.get().setGameOver(true);
gameRepository.save(game.get());
saveStatistics(game.get());
}else{
return gameResponseDtoMapper.apply(game.get());
}
}
return gameResponseDtoMapper.apply(gameRepository.save(Game.builder()
.user(userService.getUserByAuthentication(authentication))
.questions(new ArrayList<>())
.rounds(9)
.correctlyAnsweredQuestions(0)
.rounds(9L)
.actualRound(0L)
.correctlyAnsweredQuestions(0L)
.roundDuration(30)
.language("en")
.build()));
}

public GameResponseDto startRound(Long id, Authentication authentication) {
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
game.newRound(questionRepository.findRandomQuestion(game.getLanguage()));
if (game.isGameOver()){
Statistics statistics = Statistics.builder()
.user(game.getUser())
.correct(Long.valueOf(game.getCorrectlyAnsweredQuestions()))
.wrong(Long.valueOf(game.getRounds() - game.getCorrectlyAnsweredQuestions()))
.total(Long.valueOf(game.getRounds()))
.build();
Statistics oldStatistics = statisticsRepository.findByUserId(game.getUser().getId()).orElseThrow();
statisticsRepository.delete(oldStatistics);
oldStatistics.updateStatistics(statistics);
statisticsRepository.save(oldStatistics);
if (game.shouldBeGameOver()){
game.setGameOver(true);
gameRepository.save(game);
saveStatistics(game);
}
game.newRound(questionService.findRandomQuestion(game.getLanguage()));

return gameResponseDtoMapper.apply(gameRepository.save(game));
}

Expand All @@ -66,20 +74,53 @@ public QuestionResponseDto getCurrentQuestion(Long id, Authentication authentica
return questionResponseDtoMapper.apply(game.getCurrentQuestion());
}

public GameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){
@Transactional
public AnswerGameResponseDto answerQuestion(Long id, GameAnswerDto dto, Authentication authentication){
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
game.answerQuestion(dto.getAnswerId(), questionRepository);
return gameResponseDtoMapper.apply(game);
}
boolean wasCorrect = game.answerQuestion(dto.getAnswerId());

if (game.shouldBeGameOver()){
game.setGameOver(true);
gameRepository.save(game);
saveStatistics(game);
}

return new AnswerGameResponseDto(wasCorrect);
}
private void saveStatistics(Game game){
if (statisticsRepository.findByUserId(game.getUser().getId()).isPresent()){
Statistics statistics = statisticsRepository.findByUserId(game.getUser().getId()).get();
statistics.updateStatistics(game.getCorrectlyAnsweredQuestions(),
game.getQuestions().size()-game.getCorrectlyAnsweredQuestions(),
game.getRounds());
statisticsRepository.save(statistics);
} else {
Statistics statistics = Statistics.builder()
.user(game.getUser())
.correct(game.getCorrectlyAnsweredQuestions())
.wrong(game.getQuestions().size()-game.getCorrectlyAnsweredQuestions())
.total(game.getRounds())
.build();
statisticsRepository.save(statistics);
}
}
public GameResponseDto changeLanguage(Long id, String language, Authentication authentication) {
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
if(game.isGameOver()){
throw new IllegalStateException("Cannot change language after the game is over!");
}
game.setLanguage(language);
return gameResponseDtoMapper.apply(gameRepository.save(game));
}

public GameResponseDto getGameDetails(Long id, Authentication authentication) {
return gameResponseDtoMapper.apply(gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow());
Game game = gameRepository.findByIdForUser(id, userService.getUserByAuthentication(authentication).getId()).orElseThrow();
if (game.shouldBeGameOver()){
game.setGameOver(true);
gameRepository.save(game);
saveStatistics(game);
}
return gameResponseDtoMapper.apply(game);
}

public List<QuestionCategory> getQuestionCategories() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package lab.en2b.quizapi.game.dtos;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@AllArgsConstructor
@Data
@NoArgsConstructor
public class AnswerGameResponseDto {
@JsonProperty("was_correct")
private boolean wasCorrect;
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,22 +22,23 @@ public class GameResponseDto {
private UserResponseDto user;

@Schema(description = "Total rounds for the game", example = "9")
private int rounds;
private Long rounds;

@Schema(description = "Actual round for the game", example = "3")
private int actualRound;
@JsonProperty("actual_round")
private Long actualRound;

@Schema(description = "Number of correct answered questions", example = "2")
@JsonProperty("correctly_answered_questions")
private int correctlyAnsweredQuestions;
private Long correctlyAnsweredQuestions;

@Schema(description = "Moment when the timer has started", example = "LocalDateTime.now()")
@JsonProperty("round_start_time")
private LocalDateTime roundStartTime;

@Schema(description = "Number of seconds for the player to answer the question", example = "20")
@JsonProperty("round_duration")
private int roundDuration;
private Integer roundDuration;

@Schema(description = "Whether the game has finished or not", example = "true")
private boolean isGameOver;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,6 @@ public class Question {
@Enumerated(EnumType.STRING)
@Column(name = "question_category")
private QuestionCategory questionCategory;
@Column(name = "answer_category")
private AnswerCategory answerCategory;
private String language;
@Enumerated(EnumType.STRING)
private QuestionType type;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package lab.en2b.quizapi.questions.question;

import lab.en2b.quizapi.commons.exceptions.InternalApiErrorException;
import lab.en2b.quizapi.questions.answer.Answer;
import lab.en2b.quizapi.questions.answer.AnswerRepository;
import lab.en2b.quizapi.questions.answer.dtos.AnswerDto;
Expand All @@ -21,6 +22,12 @@ public class QuestionService {
private final QuestionRepository questionRepository;
private final QuestionResponseDtoMapper questionResponseDtoMapper;

/**
* Answer a question
* @param id The id of the question
* @param answerDto The answer dto
* @return The response dto
*/
public AnswerCheckResponseDto answerQuestion(Long id, AnswerDto answerDto) {
Question question = questionRepository.findById(id).orElseThrow();
if(question.getCorrectAnswer().getId().equals(answerDto.getAnswerId())){
Expand All @@ -35,13 +42,24 @@ else if(question.getAnswers().stream().noneMatch(i -> i.getId().equals(answerDto
}

public QuestionResponseDto getRandomQuestion(String lang) {
return questionResponseDtoMapper.apply(findRandomQuestion(lang));
}

/**
* Find a random question for the specified language
* @param lang The language to find the question for
* @return The random question
*/
public Question findRandomQuestion(String lang){
if (lang==null || lang.isBlank()) {
lang = "en";
}
Question q = questionRepository.findRandomQuestion(lang);
if(q==null) {
throw new InternalApiErrorException("No questions found for the specified language!");
}
loadAnswers(q);

return questionResponseDtoMapper.apply(q);
return q;
}

public QuestionResponseDto getQuestionById(Long id) {
Expand All @@ -53,7 +71,8 @@ public QuestionResponseDto getQuestionById(Long id) {
* Load the answers for a question (The distractors and the correct one)
* @param question The question to load the answers for
*/
public void loadAnswers(Question question) {
//TODO: CHAPUZAS, FIXEAR ESTO
private void loadAnswers(Question question) {
// Create the new answers list with the distractors
List<Answer> answers = new ArrayList<>(QuestionHelper.getDistractors(answerRepository, question));
// Add the correct
Expand Down
Loading

0 comments on commit 227ce1e

Please sign in to comment.