diff --git a/api/src/main/java/lab/en2b/quizapi/questions/answer/Answer.java b/api/src/main/java/lab/en2b/quizapi/questions/answer/Answer.java new file mode 100644 index 00000000..024f77d8 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/answer/Answer.java @@ -0,0 +1,28 @@ +package lab.en2b.quizapi.questions.answer; + +import jakarta.persistence.*; +import lab.en2b.quizapi.questions.question.Question; +import lombok.*; + +import java.util.List; + +@Entity +@Table(name = "answers") +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class Answer { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) + private Long id; + private String text; + private AnswerCategory category; + @OneToMany(mappedBy = "correctAnswer", fetch = FetchType.EAGER) + private List questions; + + @ManyToMany(mappedBy = "answers", fetch = FetchType.EAGER) + private List questionsWithThisAnswer; + +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java b/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java new file mode 100644 index 00000000..13412a21 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/answer/AnswerCategory.java @@ -0,0 +1,5 @@ +package lab.en2b.quizapi.questions.answer; + +public enum AnswerCategory { + CITY, COUNTRY, PERSON, EVENT, DATE, OTHER +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/answer/dtos/AnswerDto.java b/api/src/main/java/lab/en2b/quizapi/questions/answer/dtos/AnswerDto.java new file mode 100644 index 00000000..b932f0a9 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/answer/dtos/AnswerDto.java @@ -0,0 +1,14 @@ +package lab.en2b.quizapi.questions.answer.dtos; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class AnswerDto { + @JsonProperty("answer_id") + private Long answerId; +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/answer/dtos/AnswerResponseDto.java b/api/src/main/java/lab/en2b/quizapi/questions/answer/dtos/AnswerResponseDto.java new file mode 100644 index 00000000..6915567a --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/answer/dtos/AnswerResponseDto.java @@ -0,0 +1,15 @@ +package lab.en2b.quizapi.questions.answer.dtos; + +import lab.en2b.quizapi.questions.answer.AnswerCategory; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@AllArgsConstructor +@NoArgsConstructor +@Getter +public class AnswerResponseDto { + private Long id; + private String text; + private AnswerCategory category; +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/answer/mappers/AnswerResponseDtoMapper.java b/api/src/main/java/lab/en2b/quizapi/questions/answer/mappers/AnswerResponseDtoMapper.java new file mode 100644 index 00000000..0a84e756 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/answer/mappers/AnswerResponseDtoMapper.java @@ -0,0 +1,15 @@ +package lab.en2b.quizapi.questions.answer.mappers; + +import lab.en2b.quizapi.questions.answer.Answer; +import lab.en2b.quizapi.questions.answer.dtos.AnswerResponseDto; +import org.springframework.stereotype.Service; + +import java.util.function.Function; + +@Service +public class AnswerResponseDtoMapper implements Function { + @Override + public AnswerResponseDto apply(Answer answer) { + return null; + } +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java b/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java new file mode 100644 index 00000000..3aa8c83b --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/Question.java @@ -0,0 +1,42 @@ +package lab.en2b.quizapi.questions.question; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lab.en2b.quizapi.questions.answer.Answer; +import lab.en2b.quizapi.questions.answer.AnswerCategory; +import lombok.*; + +import java.util.List; + +@Entity +@Table(name = "questions") +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +public class Question { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) + private Long id; + private String content; + @NotNull + @ManyToMany(fetch = FetchType.EAGER) + @JoinTable(name="questions_answers", + joinColumns= + @JoinColumn(name="question_id", referencedColumnName="id"), + inverseJoinColumns= + @JoinColumn(name="answer_id", referencedColumnName="id") + ) + private List answers; + @ManyToOne + @JoinColumn(name = "correct_answer_id") + private Answer correctAnswer; + private QuestionCategory questionCategory; + private AnswerCategory answerCategory; + private String language; + private QuestionType type; + + +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionCategory.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionCategory.java new file mode 100644 index 00000000..9d5eb02f --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionCategory.java @@ -0,0 +1,5 @@ +package lab.en2b.quizapi.questions.question; + +public enum QuestionCategory { + HISTORY, GEOGRAPHY, SCIENCE, MATH, LITERATURE, ART, SPORTS, MUSIC, MOVIES, TV, POLITICS, OTHER +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionController.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionController.java index 1c1e7127..d9434201 100644 --- a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionController.java +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionController.java @@ -1,14 +1,38 @@ package lab.en2b.quizapi.questions.question; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import lab.en2b.quizapi.questions.answer.dtos.AnswerDto; +import lab.en2b.quizapi.questions.question.dtos.AnswerCheckResponseDto; +import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController @RequestMapping("/questions") +@RequiredArgsConstructor public class QuestionController { - @GetMapping("/dummy") - private String getDummyQuestion(){ - return "Who the hell is Steve Jobs?"; + private final QuestionService questionService; + + // TODO: REMOVE WHEN NOT USED FOR TESTING + @GetMapping + private ResponseEntity> getQuestions() { + return ResponseEntity.ok(questionService.getQuestions()); + } + + @PostMapping("/{questionId}/answer") + private ResponseEntity answerQuestion(@PathVariable Long questionId, @RequestBody AnswerDto answerDto){ + return ResponseEntity.ok(questionService.answerQuestion(questionId,answerDto)); + } + + @GetMapping("/new") + private ResponseEntity generateQuestion(){ + return ResponseEntity.ok(questionService.getQuestion()); + } + + @GetMapping("/{id}") + private ResponseEntity getQuestionById(@PathVariable Long id){ + return ResponseEntity.ok(questionService.getQuestionById(id)); } } diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java new file mode 100644 index 00000000..d957acb0 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionRepository.java @@ -0,0 +1,9 @@ +package lab.en2b.quizapi.questions.question; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface QuestionRepository extends JpaRepository { + @Query(value = "SELECT * FROM questions ORDER BY RAND() LIMIT 1", nativeQuery = true) + Question findRandomQuestion(); +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java new file mode 100644 index 00000000..fa179878 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionService.java @@ -0,0 +1,42 @@ +package lab.en2b.quizapi.questions.question; + +import lab.en2b.quizapi.questions.answer.dtos.AnswerDto; +import lab.en2b.quizapi.questions.question.dtos.AnswerCheckResponseDto; +import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; +import lab.en2b.quizapi.questions.question.mappers.QuestionResponseDtoMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class QuestionService { + + private final QuestionRepository questionRepository; + private final QuestionResponseDtoMapper questionResponseDtoMapper; + public List getQuestions() { + return questionRepository.findAll().stream().map(questionResponseDtoMapper).toList(); + } + + public AnswerCheckResponseDto answerQuestion(Long id, AnswerDto answerDto) { + Question question = questionRepository.findById(id).orElseThrow(); + if(question.getCorrectAnswer().getId().equals(answerDto.getAnswerId())){ + return new AnswerCheckResponseDto(true); + } + else if(question.getAnswers().stream().noneMatch(i -> i.getId().equals(answerDto.getAnswerId()))){ + throw new IllegalArgumentException("The answer you provided is not one of the options"); + } + else { + return new AnswerCheckResponseDto(false); + } + } + + public QuestionResponseDto getQuestion() { + return questionResponseDtoMapper.apply(questionRepository.findRandomQuestion()); + } + + public QuestionResponseDto getQuestionById(Long id) { + return questionResponseDtoMapper.apply(questionRepository.findById(id).orElseThrow()); + } +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionType.java b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionType.java new file mode 100644 index 00000000..06b28af0 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/QuestionType.java @@ -0,0 +1,6 @@ +package lab.en2b.quizapi.questions.question; + +public enum QuestionType { + TEXT, VIDEO, IMAGE, AUDIO + +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/dtos/AnswerCheckResponseDto.java b/api/src/main/java/lab/en2b/quizapi/questions/question/dtos/AnswerCheckResponseDto.java new file mode 100644 index 00000000..ea1ed4e9 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/dtos/AnswerCheckResponseDto.java @@ -0,0 +1,12 @@ +package lab.en2b.quizapi.questions.question.dtos; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class AnswerCheckResponseDto { + private boolean wasCorrect; +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/dtos/QuestionResponseDto.java b/api/src/main/java/lab/en2b/quizapi/questions/question/dtos/QuestionResponseDto.java new file mode 100644 index 00000000..3cad27cb --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/dtos/QuestionResponseDto.java @@ -0,0 +1,23 @@ +package lab.en2b.quizapi.questions.question.dtos; + +import lab.en2b.quizapi.questions.answer.AnswerCategory; +import lab.en2b.quizapi.questions.answer.dtos.AnswerResponseDto; +import lab.en2b.quizapi.questions.question.QuestionCategory; +import lab.en2b.quizapi.questions.question.QuestionType; +import lombok.Builder; + +import java.util.List; + +@Builder +public class QuestionResponseDto { + private Long id; + private String content; + private List answers; + private QuestionCategory questionCategory; + private AnswerCategory answerCategory; + private String language; + private QuestionType type; + + + +} diff --git a/api/src/main/java/lab/en2b/quizapi/questions/question/mappers/QuestionResponseDtoMapper.java b/api/src/main/java/lab/en2b/quizapi/questions/question/mappers/QuestionResponseDtoMapper.java new file mode 100644 index 00000000..2752f4e3 --- /dev/null +++ b/api/src/main/java/lab/en2b/quizapi/questions/question/mappers/QuestionResponseDtoMapper.java @@ -0,0 +1,25 @@ +package lab.en2b.quizapi.questions.question.mappers; + +import lab.en2b.quizapi.questions.answer.mappers.AnswerResponseDtoMapper; +import lab.en2b.quizapi.questions.question.Question; +import lab.en2b.quizapi.questions.question.dtos.QuestionResponseDto; +import org.springframework.stereotype.Service; + +import java.util.function.Function; + +@Service +public class QuestionResponseDtoMapper implements Function { + + @Override + public QuestionResponseDto apply(Question question) { + return QuestionResponseDto.builder() + .id(question.getId()) + .content(question.getContent()) + .type(question.getType()) + .answerCategory(question.getAnswerCategory()) + .answers(question.getAnswers().stream().map(new AnswerResponseDtoMapper()).toList()) + .language(question.getLanguage()) + .questionCategory(question.getQuestionCategory()) + .build(); + } +} diff --git a/api/src/test/java/lab/en2b/quizapi/QuizApiApplicationTests.java b/api/src/test/java/lab/en2b/quizapi/QuizApiApplicationTests.java index 42b6c339..0d641438 100644 --- a/api/src/test/java/lab/en2b/quizapi/QuizApiApplicationTests.java +++ b/api/src/test/java/lab/en2b/quizapi/QuizApiApplicationTests.java @@ -1,6 +1,5 @@ package lab.en2b.quizapi; -import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest diff --git a/api/src/test/java/lab/en2b/quizapi/questions/QuestionControllerTest.java b/api/src/test/java/lab/en2b/quizapi/questions/QuestionControllerTest.java new file mode 100644 index 00000000..e966df84 --- /dev/null +++ b/api/src/test/java/lab/en2b/quizapi/questions/QuestionControllerTest.java @@ -0,0 +1,32 @@ +package lab.en2b.quizapi.questions; + +import lab.en2b.quizapi.auth.config.SecurityConfig; +import lab.en2b.quizapi.auth.jwt.JwtUtils; +import lab.en2b.quizapi.questions.question.QuestionController; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.context.annotation.Import; +import org.springframework.test.web.servlet.MockMvc; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(QuestionController.class) +@AutoConfigureMockMvc +@Import(SecurityConfig.class) +public class QuestionControllerTest { + @Autowired + MockMvc mockMvc; + @Mock + JwtUtils jwtUtils; + @Test + void getQuestionShouldReturn200() throws Exception { + mockMvc.perform(get("/questions") + .contentType("application/json") + .with(csrf())) + .andExpect(status().isOk()); + } +}