Skip to content

Commit 7577991

Browse files
Merge pull request #29 from Wassim-Rached/staging
Prepare for v1.1.4
2 parents fc0391f + 6c5a36e commit 7577991

28 files changed

+1237
-332
lines changed

.deepsource.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,9 @@ enabled = true
1717

1818
[[analyzers]]
1919
name = "shell"
20-
enabled = true
20+
enabled = true
21+
22+
[[rules]]
23+
analyzer = "java"
24+
enabled = true
25+
name = "doc-missing"

.github/workflows/continuous_delivery_staging.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ jobs:
2020
distribution: 'corretto'
2121
java-version: 21
2222

23+
- name: Clean the application
24+
run: mvn clean
25+
2326
- name: Test the application
2427
run: mvn -B test --file pom.xml
2528

2629
- name: Build the application
27-
run: |
28-
mvn clean
29-
mvn -B package --file pom.xml
30+
run: mvn -B package --file pom.xml
3031

3132
- name: Build Docker Image
3233
uses: docker/build-push-action@v2

src/main/java/org/wa55death405/quizhub/advices/ControllerExceptionHandler.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.springframework.http.ResponseEntity;
66
import org.springframework.web.bind.annotation.ControllerAdvice;
77
import org.springframework.web.bind.annotation.ExceptionHandler;
8+
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
89
import org.wa55death405.quizhub.dto.StandardApiResponse;
910
import org.wa55death405.quizhub.enums.StandardApiStatus;
1011
import org.wa55death405.quizhub.exceptions.InputValidationException;
@@ -18,6 +19,12 @@ this class is the controller advice
1819
it is used to handle the exceptions thrown by the controllers
1920
*/
2021

22+
/*
23+
TODO:
24+
separate the exception handlers into different classes
25+
* one for predefined spring or library exceptions
26+
* one for custom exceptions
27+
*/
2128
@ControllerAdvice
2229
public class ControllerExceptionHandler {
2330

@@ -40,4 +47,9 @@ public ResponseEntity<StandardApiResponse<Void>> handleRateLimitReachedException
4047
public ResponseEntity<StandardApiResponse<Void>> handleFileNotFoundException(FileNotFoundException e) {
4148
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.FAILURE, "File not found"), HttpStatus.NOT_FOUND);
4249
}
50+
51+
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
52+
public ResponseEntity<StandardApiResponse<Void>> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException e) {
53+
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.FAILURE, "Invalid value for parameter "+ e.getPropertyName() +". Expected type: "+ e.getRequiredType()), HttpStatus.BAD_REQUEST);
54+
}
4355
}

src/main/java/org/wa55death405/quizhub/controllers/DevController.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
import jakarta.transaction.Transactional;
44
import lombok.RequiredArgsConstructor;
55
import org.springframework.context.annotation.Profile;
6-
import org.springframework.web.bind.annotation.PathVariable;
7-
import org.springframework.web.bind.annotation.PostMapping;
8-
import org.springframework.web.bind.annotation.RestController;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.*;
9+
import org.wa55death405.quizhub.dto.StandardApiResponse;
910
import org.wa55death405.quizhub.dto.questionAttempt.QuestionAttemptSubmissionDTO;
11+
import org.wa55death405.quizhub.dto.quiz.QuizCreationDTO;
12+
import org.wa55death405.quizhub.enums.StandardApiStatus;
1013
import org.wa55death405.quizhub.interfaces.utils.IFakeDataLogicalGenerator;
1114
import org.wa55death405.quizhub.repositories.QuizAttemptRepository;
1215
import org.wa55death405.quizhub.services.QuizService;
@@ -23,6 +26,7 @@ this class is the controller of the dev
2326
@RestController
2427
@RequiredArgsConstructor
2528
@Profile("dev")
29+
@RequestMapping("/api/dev")
2630
public class DevController {
2731
private final QuizService quizService;
2832
private final QuizAttemptRepository quizAttemptRepository;
@@ -44,4 +48,15 @@ public void submitPerfectResponse(@PathVariable UUID quizAttemptId) {
4448
List<QuestionAttemptSubmissionDTO> attempts = fakeDataLogicalGenerator.getPerfectScoreQuestionAttemptSubmissionDTOsForQuiz(quiz);
4549
quizService.submitQuestionAttempts(attempts,quizAttemptId);
4650
}
51+
52+
/*
53+
this api is used to get the needed information to
54+
be able to create certain quiz that already exists
55+
@Param quizId the id of the quiz
56+
@return the needed information to create the quiz
57+
*/
58+
@GetMapping("/quiz/{quizId}/creation-info")
59+
public ResponseEntity<StandardApiResponse<QuizCreationDTO>> getQuizCreationInfo(@PathVariable UUID quizId) {
60+
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.SUCCESS, "Quiz creation info fetched successfully", quizService.getQuizCreationInfo(quizId)), HttpStatus.OK);
61+
}
4762
}

src/main/java/org/wa55death405/quizhub/controllers/QuizController.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.springframework.http.HttpStatus;
55
import org.springframework.http.ResponseEntity;
66
import org.springframework.web.bind.annotation.*;
7+
import org.wa55death405.quizhub.dto.StandardPageList;
78
import org.wa55death405.quizhub.dto.questionAttempt.QuestionAttemptSubmissionDTO;
89
import org.wa55death405.quizhub.dto.quiz.QuizCreationDTO;
910
import org.wa55death405.quizhub.dto.StandardApiResponse;
@@ -32,11 +33,16 @@ public class QuizController {
3233
/*
3334
this api is used to get search for quizzes
3435
it takes the title of the quiz as a query parameter
36+
along with the page number and the size of the page
3537
and returns a list of quizzes that match the title
3638
*/
3739
@GetMapping("/search")
38-
public ResponseEntity<StandardApiResponse<List<QuizGeneralInfoDTO>>> searchQuizzes(@RequestParam(required = false,defaultValue = "") String title) {
39-
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.SUCCESS,"Quizzes fetched successfully",quizService.searchQuizzes(title)), HttpStatus.OK);
40+
public ResponseEntity<StandardApiResponse<StandardPageList<QuizGeneralInfoDTO>>> searchQuizzes(
41+
@RequestParam(defaultValue = "") String title,
42+
@RequestParam(defaultValue = "0") int page,
43+
@RequestParam(defaultValue = "10") int size
44+
) {
45+
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.SUCCESS,"Quizzes fetched successfully",quizService.searchQuizzes(title, page, size)), HttpStatus.OK);
4046
}
4147

4248
/*
@@ -110,4 +116,5 @@ public ResponseEntity<StandardApiResponse<QuizAttemptResultDTO>> getQuizAttemptR
110116
return new ResponseEntity<>(new StandardApiResponse<>(StandardApiStatus.SUCCESS,"Quiz attempt result fetched successfully",quizService.getQuizAttemptResult(quizAttemptId)), HttpStatus.OK);
111117
}
112118

119+
113120
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.wa55death405.quizhub.dto;
2+
3+
import lombok.Data;
4+
5+
import java.util.List;
6+
7+
/*
8+
* this class is used as standard response for the paginated list
9+
* @param <T> the type of the items in the list
10+
*/
11+
@Data
12+
public class StandardPageList<T>{
13+
private Integer currentPage;
14+
private Integer currentItemsSize;
15+
private Long totalItems;
16+
private List<T> items;
17+
}

src/main/java/org/wa55death405/quizhub/dto/question/QuestionCreationRequestDTO.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.wa55death405.quizhub.dto.question;
22

33
import lombok.Data;
4+
import lombok.NoArgsConstructor;
45
import org.wa55death405.quizhub.entities.*;
56
import org.wa55death405.quizhub.enums.QuestionType;
67
import org.wa55death405.quizhub.exceptions.InputValidationException;
@@ -11,15 +12,64 @@
1112
import java.util.List;
1213

1314
@Data
15+
@NoArgsConstructor
1416
public class QuestionCreationRequestDTO implements EntityDTO<Question,Quiz> {
1517
private String question;
1618
private QuestionType questionType;
1719
private Float coefficient;
1820
private String answer;
1921
private HashMap<String,Boolean> choices = new HashMap<>();
2022
private HashMap<Integer,String> orderedOptions = new HashMap<>();
23+
// HashMap<option, [match1, match2, ...]>
2124
private HashMap<String,List<String>> optionMatches = new HashMap<>();
2225

26+
/*
27+
transform the question back to
28+
the dto that was used to create it
29+
*/
30+
public QuestionCreationRequestDTO(Question question) {
31+
this.question = question.getQuestion();
32+
this.questionType = question.getQuestionType();
33+
this.coefficient = question.getCoefficient();
34+
switch (questionType) {
35+
case TRUE_FALSE,SHORT_ANSWER,NUMERIC,FILL_IN_THE_BLANK:
36+
this.answer = question.getAnswer().getAnswer();
37+
break;
38+
39+
case MULTIPLE_CHOICE:
40+
case SINGLE_CHOICE:
41+
question.getChoices().forEach(choice -> choices.put(choice.getChoice(), choice.getIsCorrect()));
42+
break;
43+
44+
case OPTION_ORDERING:
45+
question.getOrderedOptions().forEach(orderedOption -> orderedOptions.put(orderedOption.getCorrectPosition(), orderedOption.getOption()));
46+
break;
47+
48+
case OPTION_MATCHING:
49+
// TODO: this might not work as expected
50+
/*
51+
the double call on:
52+
* the getMatch().getMatch() method
53+
* and the getOption().getOption() method
54+
is because the first one is for the entity instance (Match, Option)
55+
and the second one is for the value of the entity (String)
56+
*/
57+
for (Match match : question.getMatches()){
58+
optionMatches.put(match.getMatch(), new ArrayList<>());
59+
for (CorrectOptionMatch correctOptionMatch : question.getCorrectOptionMatches()){
60+
if (correctOptionMatch.getMatch().getMatch().equals(match.getMatch())){
61+
optionMatches.get(match.getMatch()).add(correctOptionMatch.getOption().getOption());
62+
}
63+
}
64+
}
65+
break;
66+
}
67+
}
68+
69+
/*
70+
transform the dto to the question entity
71+
the quiz is used as a reference to the question
72+
*/
2373
@Override
2474
public Question toEntity(Quiz quiz) {
2575
if (coefficient <= 0) {
@@ -112,4 +162,6 @@ public Question toEntity(Quiz quiz) {
112162
}
113163
return question;
114164
}
165+
166+
115167
}

src/main/java/org/wa55death405/quizhub/dto/question/QuestionResultDTO.java

Lines changed: 40 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.wa55death405.quizhub.dto.questionAttempt.QuestionAttemptResultDTO;
1111
import org.wa55death405.quizhub.entities.*;
1212
import org.wa55death405.quizhub.enums.QuestionType;
13+
import org.wa55death405.quizhub.exceptions.InputValidationException;
1314

1415
import java.util.ArrayList;
1516
import java.util.List;
@@ -30,15 +31,15 @@ public class QuestionResultDTO {
3031
private AnswerResultDTO answer;
3132

3233
// for MULTIPLE_CHOICE, SINGLE_CHOICE
33-
private List<ChoiceResultDTO> choices = new ArrayList<>();
34+
private List<ChoiceResultDTO> choices;
3435

3536
// for OPTION_ORDERING
36-
private List<OrderedOptionResultDTO> orderedOptions = new ArrayList<>();
37+
private List<OrderedOptionResultDTO> orderedOptions;
3738

3839
// for MATCHING_OPTION
39-
private List<MatchGeneralDTO> matches = new ArrayList<>();
40-
private List<OptionGeneralDTO> options = new ArrayList<>();
41-
private List<CorrectOptionMatchResultDTO> correctOptionMatches = new ArrayList<>();
40+
private List<MatchGeneralDTO> matches;
41+
private List<OptionGeneralDTO> options;
42+
private List<CorrectOptionMatchResultDTO> correctOptionMatches;
4243

4344
// to show the play's attempt
4445
private QuestionAttemptResultDTO questionAttempt;
@@ -48,37 +49,47 @@ public QuestionResultDTO(Question question,QuestionAttempt questionAttempt) {
4849
this.question = question.getQuestion();
4950
this.coefficient = question.getCoefficient();
5051
this.questionType = question.getQuestionType();
51-
// I know this is ugly, but I have to do this
52+
5253
if (questionAttempt != null){
5354
this.questionAttempt = new QuestionAttemptResultDTO(questionAttempt);
5455
}
55-
if (question.getAnswer() != null){
56-
this.answer = new AnswerResultDTO(question.getAnswer());
57-
}
58-
if (question.getChoices() != null) {
59-
for (Choice choice : question.getChoices()) {
60-
choices.add(new ChoiceResultDTO(choice));
61-
}
62-
}
63-
if (question.getOrderedOptions() != null) {
64-
for (OrderedOption orderedOption : question.getOrderedOptions()) {
65-
orderedOptions.add(new OrderedOptionResultDTO(orderedOption));
56+
57+
switch (this.questionType){
58+
case TRUE_FALSE,FILL_IN_THE_BLANK,NUMERIC,SHORT_ANSWER->{
59+
if (question.getAnswer() == null)
60+
throw new InputValidationException("Answer is required for question of type " + this.questionType);
61+
this.answer = new AnswerResultDTO(question.getAnswer());
6662
}
67-
}
68-
if (question.getMatches() != null) {
69-
for (Match match : question.getMatches()) {
70-
matches.add(new MatchGeneralDTO(match));
63+
case MULTIPLE_CHOICE,SINGLE_CHOICE -> {
64+
choices = new ArrayList<>();
65+
for (Choice choice : question.getChoices()) {
66+
choices.add(new ChoiceResultDTO(choice));
67+
}
7168
}
72-
}
73-
if (question.getOptions() != null) {
74-
for (Option option : question.getOptions()) {
75-
options.add(new OptionGeneralDTO(option));
69+
case OPTION_MATCHING -> {
70+
matches = new ArrayList<>();
71+
options = new ArrayList<>();
72+
correctOptionMatches = new ArrayList<>();
73+
74+
for (Match match : question.getMatches()) {
75+
matches.add(new MatchGeneralDTO(match));
76+
}
77+
78+
for (Option option : question.getOptions()) {
79+
options.add(new OptionGeneralDTO(option));
80+
}
81+
82+
for (CorrectOptionMatch correctOptionMatch : question.getCorrectOptionMatches()) {
83+
correctOptionMatches.add(new CorrectOptionMatchResultDTO(correctOptionMatch));
84+
}
7685
}
77-
}
78-
if (question.getCorrectOptionMatches() != null) {
79-
for (CorrectOptionMatch correctOptionMatch : question.getCorrectOptionMatches()) {
80-
correctOptionMatches.add(new CorrectOptionMatchResultDTO(correctOptionMatch));
86+
case OPTION_ORDERING -> {
87+
orderedOptions = new ArrayList<>();
88+
for (OrderedOption orderedOption : question.getOrderedOptions()) {
89+
orderedOptions.add(new OrderedOptionResultDTO(orderedOption));
90+
}
8191
}
92+
default -> {throw new IllegalArgumentException("Invalid question type");}
8293
}
8394
}
8495
}

src/main/java/org/wa55death405/quizhub/dto/question/QuestionTakingDTO.java

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ public class QuestionTakingDTO {
2626
private QuestionType questionType;
2727

2828
// For MULTIPLE_CHOICE and SINGLE_CHOICE
29-
private List<ChoiceGeneralDTO> choices = new ArrayList<>();
29+
private List<ChoiceGeneralDTO> choices;
3030

3131
// For OPTION_ORDERING
32-
private List<OrderedOptionGeneralDTO> orderedOptions = new ArrayList<>();
32+
private List<OrderedOptionGeneralDTO> orderedOptions;
3333

3434
// For OPTION_MATCHING
35-
private List<MatchGeneralDTO> matches = new ArrayList<>();
36-
private List<OptionGeneralDTO> options = new ArrayList<>();
35+
private List<MatchGeneralDTO> matches;
36+
private List<OptionGeneralDTO> options;
3737

3838
// For previous attempts
3939
private QuestionAttemptTakingDTO questionAttempt;
@@ -46,16 +46,24 @@ public QuestionTakingDTO(Question question, QuestionAttempt questionAttempt){
4646
if (questionAttempt != null){
4747
this.questionAttempt = new QuestionAttemptTakingDTO(questionAttempt);
4848
}
49-
this.choices = question.getChoices().stream()
49+
50+
if (!question.getChoices().isEmpty())
51+
this.choices = question.getChoices().stream()
5052
.map(ChoiceGeneralDTO::new)
5153
.toList();
52-
this.orderedOptions = question.getOrderedOptions().stream()
54+
55+
if (!question.getOrderedOptions().isEmpty())
56+
this.orderedOptions = question.getOrderedOptions().stream()
5357
.map(OrderedOptionGeneralDTO::new)
5458
.toList();
55-
this.matches = question.getMatches().stream()
59+
60+
if (!question.getMatches().isEmpty())
61+
this.matches = question.getMatches().stream()
5662
.map(MatchGeneralDTO::new)
5763
.toList();
58-
this.options = question.getOptions().stream()
64+
65+
if (!question.getOptions().isEmpty())
66+
this.options = question.getOptions().stream()
5967
.map(OptionGeneralDTO::new)
6068
.toList();
6169
}

0 commit comments

Comments
 (0)