diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/CorsConfig.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/CorsConfig.java index 7cbb3737e22e405e9d2d01a81406d8bd01cf7509..bf1682ee222690a27cd9e2a71a95b1ee26aa2490 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/CorsConfig.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/CorsConfig.java @@ -9,7 +9,7 @@ public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") - .allowedOrigins("http://localhost:4173") + .allowedOrigins("http://localhost:5173") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*"); } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/SecurityConfiguration.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/SecurityConfiguration.java index a39f0d63a7f8c94e612d44fa0a0316212b25c28c..36ca4d68058eb300256a65e4c5bacbb457d2c020 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/SecurityConfiguration.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/configuration/SecurityConfiguration.java @@ -1,6 +1,5 @@ package edu.ntnu.idatt2105.configuration; - import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.RSAKey; @@ -27,6 +26,7 @@ import org.springframework.security.oauth2.server.resource.authentication.JwtAut import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; import org.springframework.security.web.SecurityFilterChain; + @Configuration public class SecurityConfiguration { @@ -36,11 +36,6 @@ public class SecurityConfiguration { this.keys = keys; } - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - @Bean public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder encoder) { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); @@ -48,6 +43,10 @@ public class SecurityConfiguration { daoAuthenticationProvider.setPasswordEncoder(encoder); return new ProviderManager(daoAuthenticationProvider); } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { @@ -91,3 +90,6 @@ public class SecurityConfiguration { return jwtAuthenticationConverter; } } + + + diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/AuthenticationController.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/AuthenticationController.java index 661fa3f7696a49967439fc5d6021a527894b132b..e6c290df0fe5f23ab3390eff7dc774f918431cdd 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/AuthenticationController.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/AuthenticationController.java @@ -45,4 +45,4 @@ public class AuthenticationController { return new TokenDTO(token); } -} +} \ No newline at end of file diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionAnswerController.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionAnswerController.java index 5159ea07a3676421abd52eef95f611ac8c8009ff..fe466b1c390fa359681f4fa21be81cb65d44aae0 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionAnswerController.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionAnswerController.java @@ -1,22 +1,29 @@ package edu.ntnu.idatt2105.controller; import edu.ntnu.idatt2105.dto.QuestionAnswerDTO; +import edu.ntnu.idatt2105.exception.QuestionNotFoundException; +import edu.ntnu.idatt2105.model.Question; import edu.ntnu.idatt2105.model.QuestionAnswer; import edu.ntnu.idatt2105.service.QuestionAnswerService; +import edu.ntnu.idatt2105.service.QuestionService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.Optional; + @RestController @RequestMapping("/question-answers") public class QuestionAnswerController { private final QuestionAnswerService questionAnswerService; + private QuestionService questionService; @Autowired - public QuestionAnswerController(QuestionAnswerService questionAnswerService) { + public QuestionAnswerController(QuestionAnswerService questionAnswerService, QuestionService questionService) { this.questionAnswerService = questionAnswerService; + this.questionService = questionService; } @PostMapping("/save") @@ -26,6 +33,12 @@ public class QuestionAnswerController { return convertToQuestionAnswerDTO(savedAnswer); } + @GetMapping("/is-correct") +public ResponseEntity<Boolean> isCorrect(@RequestBody QuestionAnswerDTO answerDTO) { + boolean correct = questionAnswerService.isCorrect(convertToQuestionAnswer(answerDTO)); + return new ResponseEntity<>(correct, HttpStatus.OK); + } + private QuestionAnswerDTO convertToQuestionAnswerDTO(QuestionAnswer questionAnswer) { QuestionAnswerDTO dto = new QuestionAnswerDTO(); dto.setId(questionAnswer.getId()); @@ -34,4 +47,16 @@ public class QuestionAnswerController { dto.setCorrect(questionAnswer.isCorrect()); return dto; } + + private QuestionAnswer convertToQuestionAnswer(QuestionAnswerDTO questionAnswerDTO) { + Optional<Question> questionOptional = Optional.ofNullable(questionService.findQuestionById(questionAnswerDTO.getQuestionId())); + Question question = questionOptional.orElseThrow(() -> + new QuestionNotFoundException("Question not found with ID: " + questionAnswerDTO.getQuestionId())); + + QuestionAnswer questionAnswer = new QuestionAnswer(); + questionAnswer.setId(questionAnswerDTO.getId()); + questionAnswer.setQuestion(question); + questionAnswer.setGivenAnswer(questionAnswerDTO.getGivenAnswer()); + return questionAnswer; + } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionController.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionController.java index 8fa11b8ea7a9490985d475f15f3a0c62953d9132..0af6f56cfe156b62a8872b270b05fcd95449b77d 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionController.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuestionController.java @@ -1,10 +1,10 @@ package edu.ntnu.idatt2105.controller; import edu.ntnu.idatt2105.dto.QuestionDTO; -import edu.ntnu.idatt2105.model.MultipleChoiceQuestion; import edu.ntnu.idatt2105.model.Question; import edu.ntnu.idatt2105.service.QuestionService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -22,36 +22,38 @@ public class QuestionController { this.questionService = questionService; } - @PostMapping - public QuestionDTO createMCQuestion(@RequestBody QuestionDTO questionDTO) { - MultipleChoiceQuestion question = questionService.createMCQuestion(questionDTO); - return convertToDTO(question); + @PostMapping("/save") + public QuestionDTO saveQuestion(@RequestBody QuestionDTO questionDTO) { + Question question = questionService.createOrUpdateQuestion(questionDTO); + + //TODO: make a mapper class to do this + return new QuestionDTO( + question.getId(), + question.getQuestionText(), + question.getType(), + question.getAnswer(), + question.getOptionsList(), + question.getScore(), + question.getQuiz().getId() + ); } - @PostMapping("/update") - public QuestionDTO updateQuestion(@RequestBody QuestionDTO questionDTO) { - return questionService.updateQuestion(questionDTO); - } - - @PostMapping("/delete") - public void deleteQuestion(@RequestBody Map<String, Integer> payload) { - questionService.deleteQuestion(payload.get("id")); - } - - @GetMapping("/list") - public List<QuestionDTO> getQuestionsByQuizId(@RequestParam Integer quizId) { - return questionService.findAllQuestionsByQuizId(quizId).stream() - .map((Question question) -> convertToDTO((MultipleChoiceQuestion) question)) - .collect(Collectors.toList()); - } - - public QuestionDTO convertToDTO(MultipleChoiceQuestion question) { - QuestionDTO questionDTO = new QuestionDTO(); - questionDTO.setId(question.getId()); - questionDTO.setQuestionText(question.getQuestionText()); - questionDTO.setScore(question.getScore()); - questionDTO.setAnswerOptions(question.getAnswerOptions()); - questionDTO.setCorrectAnswerIndex(question.getCorrectAnswerIndex()); - return questionDTO; + @GetMapping("/get/{questionId}") + public QuestionDTO getQuestion(@PathVariable Integer questionId) { + Question question = questionService.findQuestionById(questionId); + return new QuestionDTO( + question.getId(), + question.getQuestionText(), + question.getType(), + question.getAnswer(), + question.getOptionsList(), + question.getScore(), + question.getQuiz().getId() + ); + } + + @PostMapping("/delete/{questionId}") + public void deleteQuestion(@PathVariable Integer questionId) { + questionService.deleteQuestion(questionId); } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizController.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizController.java index 342ae4696c249757063903f44cd75bd435ae50b0..6935d07efd420bbb5a49437d4ed158515b9e0fa4 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizController.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizController.java @@ -30,12 +30,14 @@ public class QuizController { @PostMapping("/create") public QuizDTO createQuiz(@RequestBody QuizDTO quizDTO) { - Quiz quiz = quizService.createQuiz(quizDTO); - return convertToDTO(quiz); + return quizService.createQuiz(quizDTO); } @PostMapping("/update") public QuizDTO updateQuiz(@RequestBody QuizDTO quizDTO) { + if (quizDTO.getId() == null) { + throw new IllegalArgumentException("Quiz ID must be provided for update."); + } return quizService.updateQuiz(quizDTO); } @@ -44,8 +46,8 @@ public class QuizController { quizService.deleteQuiz(payload.get("id")); } - @GetMapping("/quiz") - public QuizDTO getQuizById(@RequestParam Integer quizId) { + @GetMapping("/quiz/{quizId}") + public QuizDTO getQuizById(@PathVariable Integer quizId) { return convertToDTO(quizService.findQuizById(quizId)); } @@ -71,11 +73,11 @@ public class QuizController { .collect(Collectors.toList()); } - @GetMapping("/creator") - public List<QuizDTO> getQuizzesByCreatorId(@RequestParam Integer creatorId) { + @GetMapping("/creator/{creatorId}") + public List<QuizDTO> getQuizzesByCreatorId(@PathVariable Integer creatorId) { return quizService.findAllQuizzesByCreatorId(creatorId).stream() - .map(this::convertToDTO) - .collect(Collectors.toList()); + .map(this::convertToDTO) + .collect(Collectors.toList()); } public QuizDTO convertToDTO(Quiz quiz) { @@ -83,10 +85,6 @@ public class QuizController { quizDTO.setId(quiz.getId()); quizDTO.setTitle(quiz.getTitle()); quizDTO.setCategory(quiz.getCategory()); - List<Integer> questionIds = quiz.getQuestions().stream() - .map(Question::getId) - .collect(Collectors.toList()); - quizDTO.setQuestionIds(questionIds); return quizDTO; } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizResultController.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizResultController.java index 235c131d2821a86d303f1eac63520b8b02a70000..1cc8e590d9792cc83c8abfbba9b4ed8d5888ab71 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizResultController.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/QuizResultController.java @@ -50,7 +50,7 @@ public class QuizResultController { QuizResultDTO quizResultDTO = new QuizResultDTO(); quizResultDTO.setQuizId(quizResult.getQuiz().getId()); quizResultDTO.setUserId(quizResult.getUser().getId()); - quizResultDTO.setScore(quizResult.getScore()); + quizResultDTO.setTotalScore(quizResult.getScore()); quizResultDTO.setStatus(quizResult.getStatus()); quizResultDTO.setStartedAt(quizResult.getStartedAt()); quizResultDTO.setCompletedAt(quizResult.getCompletedAt()); diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/UserController.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/UserController.java index 0f999f5f8f3307e0c51028de954b02df0f8b1528..55048f3d8ed537da51d74f6e1c3df0ae77118a53 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/UserController.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/controller/UserController.java @@ -1,7 +1,9 @@ package edu.ntnu.idatt2105.controller; +import edu.ntnu.idatt2105.dto.UserDTO; import edu.ntnu.idatt2105.model.User; +import edu.ntnu.idatt2105.service.TokenService; import edu.ntnu.idatt2105.service.UserService; import org.springframework.web.bind.annotation.*; @@ -13,8 +15,10 @@ import java.util.List; public class UserController { private final UserService userService; + private final TokenService tokenService; - public UserController(UserService userService) { + public UserController(UserService userService, TokenService tokenService) { + this.tokenService = tokenService; this.userService = userService; } @@ -23,4 +27,9 @@ public class UserController { public List<User> getAppUsers() { return userService.findAllAppUsers(); } + + @GetMapping("/getId/{token}") + public UserDTO getUserDTOFromToken(@PathVariable String token) { + return tokenService.getUserDTOFromToken(token); + } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuestionDTO.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuestionDTO.java index 86132ab446b07c69cbc773111103831ab4dc2fad..68e1fae7144a4cd02373bffdb7bd58418e1db676 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuestionDTO.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuestionDTO.java @@ -1,25 +1,35 @@ package edu.ntnu.idatt2105.dto; +import edu.ntnu.idatt2105.model.QuestionType; + +import java.util.Arrays; import java.util.List; +import java.util.stream.Collectors; public class QuestionDTO { private Integer id; + private Integer quizId; private String questionText; + private QuestionType type; + private String answer; + private List<String> options; private int score; - private List<String> answerOptions; - private int correctAnswerIndex; public QuestionDTO() { } - public QuestionDTO(Integer id, String questionText, int score) { + public QuestionDTO(Integer id, String questionText, QuestionType type, String answer, List<String> options, int score, Integer quizId) { this.id = id; this.questionText = questionText; + this.type = type; + this.answer = answer; + this.options = options; this.score = score; + this.quizId = quizId; } - // Getters and setters + // Getters og setters public Integer getId() { return id; } @@ -36,6 +46,30 @@ public class QuestionDTO { this.questionText = questionText; } + public QuestionType getType() { + return type; + } + + public void setType(QuestionType type) { + this.type = type; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + + public List<String> getOptions() { + return options; + } + + public void setOptions(List<String> options) { + this.options = options; + } + public int getScore() { return score; } @@ -44,21 +78,22 @@ public class QuestionDTO { this.score = score; } - public List<String> getAnswerOptions() { - return answerOptions; + public Integer getQuizId() { + return quizId; } - public void setAnswerOptions(List<String> answerOptions) { - this.answerOptions = answerOptions; + public void setQuizId(Integer quizId) { + this.quizId = quizId; } - public int getCorrectAnswerIndex() { - return correctAnswerIndex; + public void setOptionsFromString(String optionsString) { + if (optionsString != null && !optionsString.isEmpty()) { + this.options = Arrays.stream(optionsString.split("\\*")) + .collect(Collectors.toList()); + } } - public void setCorrectAnswerIndex(int correctAnswerIndex) { - this.correctAnswerIndex = correctAnswerIndex; + public String getOptionsAsString() { + return String.join("*", this.options); } - - } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizDTO.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizDTO.java index a2ecaeeca036b136b3afc2b2ea8b561708298ee1..934feae470e98cf4017d3a6f24d44d6dbce04b96 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizDTO.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizDTO.java @@ -9,7 +9,6 @@ import java.util.List; public class QuizDTO { private Integer id; private String title; - private List<Integer> questionIds; private Integer creatorId; private QuizCategory category; private QuizDifficulty difficulty; @@ -17,9 +16,8 @@ public class QuizDTO { public QuizDTO() { } - public QuizDTO(Integer id, String title, List<Integer> questionIds, Integer creatorId, QuizCategory category, QuizDifficulty difficulty) { + public QuizDTO(String title, Integer creatorId, QuizCategory category, QuizDifficulty difficulty) { this.title = title; - this.questionIds = questionIds; this.creatorId = creatorId; this.category = category; this.difficulty = difficulty; @@ -42,14 +40,6 @@ public class QuizDTO { this.title = title; } - public Iterable<Integer> getQuestionIds() { - return questionIds; - } - - public void setQuestionIds(List<Integer> questionIds) { - this.questionIds = questionIds; - } - public Integer getCreatorId() { return creatorId; } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizResultDTO.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizResultDTO.java index 37af5eee55f6eb6003dde702c187db4648d069c1..758a626d05b6b5136d92f203ec14c76b7f898044 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizResultDTO.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/dto/QuizResultDTO.java @@ -8,7 +8,7 @@ public class QuizResultDTO { private Integer quizId; private Integer userId; private List<QuestionAnswerDTO> answers; - private int score; + private int totalScore; private String status; private LocalDateTime startedAt; private LocalDateTime completedAt; @@ -17,12 +17,12 @@ public class QuizResultDTO { public QuizResultDTO() { } - public QuizResultDTO(Integer id, Integer quizId, Integer userId, List<QuestionAnswerDTO> answers, int score, String status, LocalDateTime startedAt, LocalDateTime completedAt) { + public QuizResultDTO(Integer id, Integer quizId, Integer userId, List<QuestionAnswerDTO> answers, int totalScore, String status, LocalDateTime startedAt, LocalDateTime completedAt) { this.id = id; this.quizId = quizId; this.userId = userId; this.answers = answers; - this.score = score; + this.totalScore = totalScore; this.status = status; this.startedAt = startedAt; this.completedAt = completedAt; @@ -60,12 +60,12 @@ public class QuizResultDTO { this.answers = answers; } - public int getScore() { - return score; + public int getTotalScore() { + return totalScore; } - public void setScore(int score) { - this.score = score; + public void setTotalScore(int totalScore) { + this.totalScore = totalScore; } public String getStatus() { @@ -99,7 +99,7 @@ public class QuizResultDTO { ", quizId=" + quizId + ", userId=" + userId + ", answers=" + answers + - ", score=" + score + + ", score=" + totalScore + '}'; } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/MultipleChoiceQuestion.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/MultipleChoiceQuestion.java deleted file mode 100644 index 58c809d6eedbc226131291df500b6b5ad2e8134f..0000000000000000000000000000000000000000 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/MultipleChoiceQuestion.java +++ /dev/null @@ -1,51 +0,0 @@ -package edu.ntnu.idatt2105.model; - -import jakarta.persistence.*; - -import java.util.List; - -@Entity -public class MultipleChoiceQuestion extends Question { - @ElementCollection - @CollectionTable(name = "answer_options", joinColumns = @JoinColumn(name = "question_id")) - @Column(name = "option") - private List<String> answerOptions; - - @Column(nullable = false) - private int correctAnswerIndex; - - public MultipleChoiceQuestion() { - } - - public MultipleChoiceQuestion(Quiz quiz, String questionText, int score, List<String> answerOptions, int correctAnswerIndex) { - super(quiz, questionText, score); - this.answerOptions = answerOptions; - this.correctAnswerIndex = correctAnswerIndex; - } - - public List<String> getAnswerOptions() { - return answerOptions; - } - - public void setAnswerOptions(List<String> answerOptions) { - this.answerOptions = answerOptions; - } - - public int getCorrectAnswerIndex() { - return correctAnswerIndex; - } - - public void setCorrectAnswerIndex(int correctAnswerIndex) { - this.correctAnswerIndex = correctAnswerIndex; - } - - @Override - public boolean checkAnswer(String answer) { - try { - int answerIndex = Integer.parseInt(answer); - return answerIndex == correctAnswerIndex + 1; - } catch (NumberFormatException e) { - return false; - } - } -} \ No newline at end of file diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Question.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Question.java index c6cf0ebb8bdff1c96e6c5437db92c7686a25cdb0..81d1650d655a1e49bdf475d89c5cbaf7e58d4e84 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Question.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Question.java @@ -1,10 +1,14 @@ package edu.ntnu.idatt2105.model; +import jakarta.annotation.Nullable; import jakarta.persistence.*; +import java.util.ArrayList; +import java.util.List; + @Entity @Inheritance(strategy = InheritanceType.JOINED) -public abstract class Question { +public class Question { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; @@ -19,12 +23,27 @@ public abstract class Question { @Column(nullable = false) private int score; + @Column(nullable = false) + private QuestionType type; + + @Column(nullable = false) + private String answer; + + @Column() + @Nullable + private String options; //Used for MCQ and TrueOrFalse to store answer options, divided by * + + + public Question() { } - public Question(Quiz quiz, String questionText, int score) { + public Question(String questionText, QuestionType type, int score, String answer, @Nullable String options) { this.questionText = questionText; + this.type = type; this.score = score; + this.answer = answer; + this.options = options; } public Integer getId() { @@ -51,12 +70,41 @@ public abstract class Question { this.score = score; } - public abstract boolean checkAnswer(String answer); - public Quiz getQuiz() { return quiz; } public void setQuiz(Quiz quiz) { this.quiz = quiz; } + public QuestionType getType() { + return type; + } + public void setType(QuestionType type) { + this.type = type; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } + + @Nullable + public String getOptions() { + return options; + } + + public void setOptions(@Nullable String options) { + this.options = options; + } + + // Metode for å få en liste av alle alternativer + public List<String> getOptionsList() { + if (this.options != null && !this.options.isEmpty()) { + return List.of(this.options.split("\\*")); + } + return new ArrayList<>(); + } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionAnswer.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionAnswer.java index ecbcd5c7d9ff35517383d17ee2f5a267db7e83d1..c9003406552394002083bbfd193fc403d5a50487 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionAnswer.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionAnswer.java @@ -15,9 +15,6 @@ public class QuestionAnswer { @Column(nullable = false) private String givenAnswer; - @Column(nullable = false) - private boolean correct; - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "quiz_result_id") private QuizResult quizResult; @@ -29,12 +26,6 @@ public class QuestionAnswer { public QuestionAnswer(Question question, String givenAnswer) { this.question = question; this.givenAnswer = givenAnswer; - this.correct = validateAnswer(givenAnswer); - } - - // Validation method - private boolean validateAnswer(String givenAnswer) { - return question.checkAnswer(givenAnswer); } public Integer getId() { @@ -51,8 +42,6 @@ public class QuestionAnswer { public void setQuestion(Question question) { this.question = question; - // Update 'correct' whenever the question changes - this.correct = validateAnswer(this.givenAnswer); } public String getGivenAnswer() { @@ -61,19 +50,12 @@ public class QuestionAnswer { public void setGivenAnswer(String givenAnswer) { this.givenAnswer = givenAnswer; - // Update 'correct' whenever the given answer changes - this.correct = validateAnswer(givenAnswer); } public boolean isCorrect() { - return correct; + return this.question.getAnswer().equalsIgnoreCase(this.givenAnswer); } - public void setCorrect(boolean correct) { - this.correct = correct; - } - - public QuizResult getQuizResult() { return quizResult; } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionType.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionType.java new file mode 100644 index 0000000000000000000000000000000000000000..ebdf838770633ab90bb9d4226a5bf0bff0eb7c28 --- /dev/null +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuestionType.java @@ -0,0 +1,5 @@ +package edu.ntnu.idatt2105.model; + +public enum QuestionType { + STRING, TRUE_OR_FALSE, MULTIPLE_CHOICE +} diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Quiz.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Quiz.java index fb51e9a7e139b9e132c92f9d60029786b6a4f649..129552472c8c99d4bf144116980b762f5fe8ee68 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Quiz.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Quiz.java @@ -15,11 +15,6 @@ public class Quiz { @Column(nullable = false) private String title; - @OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL) - @JoinColumn(name = "quiz_id") - private List<Question> questions; - - @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "creator_id") private User creator; @@ -33,9 +28,8 @@ public class Quiz { public Quiz() { } - public Quiz(String title, List<Question> questions, User creator, QuizCategory category, QuizDifficulty difficulty) { + public Quiz(String title, User creator, QuizCategory category, QuizDifficulty difficulty) { this.title = title; - this.questions = questions; this.creator = creator; this.category = category; this.difficulty = difficulty; @@ -58,14 +52,6 @@ public class Quiz { this.title = title; } - public List<Question> getQuestions() { - return questions; - } - - public void setQuestions(List<Question> questions) { - this.questions = questions; - } - public User getCreator() { return creator; } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuizResult.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuizResult.java index 1e8b0a7de3bb924ed5042860f3551734dbc98a09..d4d1a02319e95d2cd91b70eef797272776cb2cd8 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuizResult.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/QuizResult.java @@ -1,5 +1,6 @@ package edu.ntnu.idatt2105.model; +import jakarta.annotation.Nullable; import jakarta.persistence.*; import java.time.LocalDateTime; @@ -18,9 +19,6 @@ public class QuizResult { @JoinColumn(name = "quiz_id", nullable = false) private Quiz quiz; - @OneToMany(mappedBy = "quizResult", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) - private List<QuestionAnswer> answers = new ArrayList<>(); - @Column(nullable = false) private int score; @@ -28,7 +26,6 @@ public class QuizResult { @JoinColumn(name = "user_id") private User user; - @Column(nullable = false) private String status; @@ -36,19 +33,16 @@ public class QuizResult { private LocalDateTime startedAt; @Column + @Nullable private LocalDateTime completedAt; - - - public QuizResult() { // JPA requires a no-arg constructor } // Constructor for initializing with a quiz and optionally with answers. - public QuizResult(Quiz quiz, List<QuestionAnswer> answers, User user, String status, LocalDateTime startedAt, LocalDateTime completedAt) { + public QuizResult(Quiz quiz, User user, String status, LocalDateTime startedAt, LocalDateTime completedAt) { this.quiz = quiz; - this.setAnswers(answers); this.user = user; this.status = status; this.startedAt = startedAt; @@ -80,17 +74,6 @@ public class QuizResult { this.user = user; } - public List<QuestionAnswer> getAnswers() { - return answers; - } - - // When setting answers, also recalculate the score based on the correctness and question score. - public void setAnswers(List<QuestionAnswer> answers) { - this.answers = answers; - this.answers.forEach(answer -> answer.setQuizResult(this)); - calculateScore(); // Recalculate the score based on the new set of answers - } - public int getScore() { return score; } @@ -115,24 +98,12 @@ public class QuizResult { this.startedAt = startedAt; } + @Nullable public LocalDateTime getCompletedAt() { return completedAt; } - public void setCompletedAt(LocalDateTime completedAt) { + public void setCompletedAt(@Nullable LocalDateTime completedAt) { this.completedAt = completedAt; } - - public void addQuestionAnswer(QuestionAnswer questionAnswer) { - this.answers.add(questionAnswer); - questionAnswer.setQuizResult(this); - calculateScore(); - } - - private void calculateScore() { - this.score = this.answers.stream() - .filter(QuestionAnswer::isCorrect) - .mapToInt(answer -> answer.getQuestion().getScore()) - .sum(); - } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Role.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Role.java index 6aa56f8b41ea01f65e548bdd8221f26c8c90e4ed..eb723c75d66073a13885a64d64f0352102d8e614 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Role.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/Role.java @@ -59,4 +59,4 @@ public class Role implements GrantedAuthority { ", authority='" + authority + '\'' + '}'; } -} +} \ No newline at end of file diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/User.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/User.java index 0c09f8594980dfa3c45829c5017f6ccdd01c281a..6e89f485711baded1b5f4daa13ec388cb8338750 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/User.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/model/User.java @@ -2,11 +2,11 @@ package edu.ntnu.idatt2105.model; import jakarta.persistence.*; import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.*; - @Entity @Table(name = "users") public class User implements UserDetails { @@ -38,7 +38,6 @@ public class User implements UserDetails { public User() { - this.authorities = new HashSet<>(); } public User(String username, String password, Set<Role> authorities) { @@ -47,13 +46,6 @@ public class User implements UserDetails { this.authorities = authorities; } - public User(Integer id, String username, String password, Set<Role> authorities) { - this.id = id; - this.username = username; - this.password = password; - this.authorities = authorities; - } - public Integer getId() { return id; } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/repository/QuizRepository.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/repository/QuizRepository.java index 41d3bd6c4d3d7b1b4e98bc6267c89685f13c493a..cc9a7f5c358ea2758f8142ec740091ccf1f50832 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/repository/QuizRepository.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/repository/QuizRepository.java @@ -3,6 +3,7 @@ package edu.ntnu.idatt2105.repository; import edu.ntnu.idatt2105.model.Quiz; import edu.ntnu.idatt2105.model.QuizCategory; import edu.ntnu.idatt2105.model.QuizDifficulty; +import edu.ntnu.idatt2105.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @@ -18,4 +19,6 @@ public interface QuizRepository extends JpaRepository<Quiz, Integer> { List<Quiz> findAllByDifficulty(QuizDifficulty difficulty); List<Quiz> findAllByCreatorId(Integer creatorId); + + } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/AuthenticationService.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/AuthenticationService.java index b5ea17d65e85a3410f3a35943c2daa694c6c6ba8..af89bc337d002e5b3ff79796b63616e9c4f715d9 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/AuthenticationService.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/AuthenticationService.java @@ -11,6 +11,7 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.password.PasswordEncoder; @@ -83,4 +84,4 @@ public class AuthenticationService { return tokenService.generateJwt(auth); } -} +} \ No newline at end of file diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionAnswerService.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionAnswerService.java index c5fed258622c23416aa2272f52a2854facc3828a..6a29982703b78c307e5292e751ecc638864eddd1 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionAnswerService.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionAnswerService.java @@ -38,11 +38,11 @@ public class QuestionAnswerService { QuestionAnswer answer = new QuestionAnswer(); answer.setQuestion(question); answer.setGivenAnswer(answerDTO.getGivenAnswer()); - answer.setCorrect(question.checkAnswer(answerDTO.getGivenAnswer())); answer.setQuizResult(quizResult); - quizResult.addQuestionAnswer(answer); - return questionAnswerRepository.save(answer); } + public boolean isCorrect(QuestionAnswer questionAnswer) { + return questionAnswer.getQuestion().getAnswer().equalsIgnoreCase(questionAnswer.getGivenAnswer()); + } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionService.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionService.java index 91e584cd93467b78e2313cf1f6a53b94dbd87c87..e89f5881019f808c26dddc7ddb98aa1d2111dbbb 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionService.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuestionService.java @@ -1,39 +1,60 @@ package edu.ntnu.idatt2105.service; import edu.ntnu.idatt2105.dto.QuestionDTO; -import edu.ntnu.idatt2105.exception.QuestionNotFoundException; -import edu.ntnu.idatt2105.model.MultipleChoiceQuestion; import edu.ntnu.idatt2105.model.Question; +import edu.ntnu.idatt2105.model.QuestionType; import edu.ntnu.idatt2105.repository.QuestionRepository; -import jakarta.transaction.Transactional; +import edu.ntnu.idatt2105.repository.QuizRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; @Service public class QuestionService { private final QuestionRepository questionRepository; + private final QuizRepository quizRepository; @Autowired - public QuestionService(QuestionRepository questionRepository) { + public QuestionService(QuestionRepository questionRepository, QuizRepository quizRepository) { this.questionRepository = questionRepository; + this.quizRepository = quizRepository; } - public QuestionDTO updateQuestion(QuestionDTO questionDTO) { - MultipleChoiceQuestion question = (MultipleChoiceQuestion) questionRepository.findById(questionDTO.getId()) - .orElseThrow(() -> new QuestionNotFoundException("Question not found with id " + questionDTO.getId())); - - question.setQuestionText(questionDTO.getQuestionText()); - question.setScore(questionDTO.getScore()); - question.setAnswerOptions(questionDTO.getAnswerOptions()); - question.setCorrectAnswerIndex(questionDTO.getCorrectAnswerIndex()); - - MultipleChoiceQuestion updatedQuestion = questionRepository.save(question); - return convertToQuestionDTO(updatedQuestion); - } - + @Transactional + public Question createOrUpdateQuestion(QuestionDTO questionDTO) { + Question question; + if (questionDTO.getId() != null) { + // Oppdaterer et eksisterende spørsmål + Optional<Question> optionalQuestion = questionRepository.findById(questionDTO.getId()); + if (!optionalQuestion.isPresent()) { + return null; // Eller kaste en egendefinert unntak + } + question = optionalQuestion.get(); + } else { + // Oppretter et nytt spørsmål + question = new Question(); + } + + question.setQuestionText(questionDTO.getQuestionText()); + question.setType(questionDTO.getType()); + + if (questionDTO.getType().equals(QuestionType.MULTIPLE_CHOICE)) { + question.setOptions(questionDTO.getOptionsAsString()); + } else if (questionDTO.getType().equals(QuestionType.TRUE_OR_FALSE)) { + question.setOptions("TRUE*FALSE"); + } else { + question.setOptions(null); + } + question.setAnswer(questionDTO.getAnswer()); + question.setScore(questionDTO.getScore()); + question.setQuiz(quizRepository.findById(questionDTO.getQuizId()).orElse(null)); + + return questionRepository.save(question); + } public void deleteQuestion(Integer id) { questionRepository.deleteById(id); @@ -43,26 +64,8 @@ public class QuestionService { return questionRepository.findAllByQuizId(quizId); } - @Transactional - public MultipleChoiceQuestion createMCQuestion(QuestionDTO questionDTO) { - MultipleChoiceQuestion question = new MultipleChoiceQuestion(); - question.setQuestionText(questionDTO.getQuestionText()); - question.setScore(questionDTO.getScore()); - question.setAnswerOptions(questionDTO.getAnswerOptions()); - question.setCorrectAnswerIndex(questionDTO.getCorrectAnswerIndex()); - return questionRepository.save(question); + public Question findQuestionById(Integer id) { + Optional<Question> question = questionRepository.findById(id); + return question.orElse(null); } - - private QuestionDTO convertToQuestionDTO(MultipleChoiceQuestion question) { - QuestionDTO questionDTO = new QuestionDTO(); - questionDTO.setId(question.getId()); - questionDTO.setQuestionText(question.getQuestionText()); - questionDTO.setScore(question.getScore()); - questionDTO.setAnswerOptions(question.getAnswerOptions()); - questionDTO.setCorrectAnswerIndex(question.getCorrectAnswerIndex()); - - return questionDTO; - } - - } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizResultService.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizResultService.java index 405574475db95d873bcbc326bffe899c59d34220..fcedbdbaeda85897ab4add87f10b0d7284c430c1 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizResultService.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizResultService.java @@ -28,14 +28,11 @@ public class QuizResultService { private final UserRepository userRepository; - private final QuestionRepository questionRepository; - @Autowired - public QuizResultService(QuizResultRepository quizResultRepository, QuizRepository quizRepository, UserRepository userRepository, QuestionRepository questionRepository) { + public QuizResultService(QuizResultRepository quizResultRepository, QuizRepository quizRepository, UserRepository userRepository) { this.quizResultRepository = quizResultRepository; this.quizRepository = quizRepository; this.userRepository = userRepository; - this.questionRepository = questionRepository; } @@ -54,7 +51,6 @@ public class QuizResultService { quizResult.setUser(user); quizResult.setStatus("Påbegynt"); quizResult.setStartedAt(LocalDateTime.now()); - quizResult.setCompletedAt(creationDTO.getCompletedAt()); return quizResultRepository.save(quizResult); } @@ -81,18 +77,11 @@ public class QuizResultService { quizResultDTO.setId(quizResult.getId()); quizResultDTO.setQuizId(quizResult.getQuiz().getId()); quizResultDTO.setUserId(quizResult.getUser().getId()); - quizResultDTO.setScore(quizResult.getScore()); + quizResultDTO.setTotalScore(quizResult.getScore()); quizResultDTO.setStatus(quizResult.getStatus()); quizResultDTO.setStartedAt(quizResult.getStartedAt()); quizResultDTO.setCompletedAt(quizResult.getCompletedAt()); - // Anta at vi også trenger å konvertere listen av QuestionAnswer til QuestionAnswerDTO - // Dette krever at vi har en metode for å gjøre denne konverteringen: - List<QuestionAnswerDTO> answerDTOs = quizResult.getAnswers().stream() - .map(this::convertToQuestionAnswerDTO) - .collect(Collectors.toList()); - quizResultDTO.setAnswers(answerDTOs); - return quizResultDTO; } @@ -105,25 +94,4 @@ public class QuizResultService { return questionAnswerDTO; } - - - - - - private List<QuestionAnswer> convertToQuestionAnswers(List<QuestionAnswerDTO> answerDTOs, QuizResult quizResult) { - List<QuestionAnswer> answers = new ArrayList<>(); - for (QuestionAnswerDTO answerDTO : answerDTOs) { - QuestionAnswer answer = new QuestionAnswer(); - - MultipleChoiceQuestion question = (MultipleChoiceQuestion) questionRepository.findById(answerDTO.getQuestionId()) - .orElseThrow(() -> new QuestionNotFoundException("Question not found with id: " + answerDTO.getQuestionId())); - answer.setQuestion(question); - answer.setGivenAnswer(answerDTO.getGivenAnswer()); - answer.setCorrect(question.checkAnswer(answerDTO.getGivenAnswer())); - answer.setQuizResult(quizResult); - - answers.add(answer); - } - return answers; - } } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizService.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizService.java index 53a5a771369a395c50458f8becd04ec3b6c64955..f84cc186b18982b0900a28e39f392f782fd65e19 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizService.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/QuizService.java @@ -2,17 +2,16 @@ package edu.ntnu.idatt2105.service; import edu.ntnu.idatt2105.dto.QuizDTO; import edu.ntnu.idatt2105.exception.QuizNotFoundException; -import edu.ntnu.idatt2105.model.Question; -import edu.ntnu.idatt2105.model.Quiz; -import edu.ntnu.idatt2105.model.QuizCategory; -import edu.ntnu.idatt2105.model.QuizDifficulty; +import edu.ntnu.idatt2105.model.*; import edu.ntnu.idatt2105.repository.QuestionRepository; import edu.ntnu.idatt2105.repository.QuizRepository; +import edu.ntnu.idatt2105.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -24,39 +23,56 @@ public class QuizService { private QuestionRepository questionRepository; @Autowired - public QuizService(QuizRepository quizRepository, QuestionRepository questionRepository) { + private UserRepository userRepository; + + @Autowired + public QuizService(QuizRepository quizRepository, QuestionRepository questionRepository, UserRepository userRepository) { this.quizRepository = quizRepository; this.questionRepository = questionRepository; + this.userRepository = userRepository; } @Transactional - public Quiz createQuiz(QuizDTO quizDTO) { + public QuizDTO createQuiz(QuizDTO quizDTO) { Quiz quiz = new Quiz(); quiz.setTitle(quizDTO.getTitle()); quiz.setCategory(quizDTO.getCategory()); + quiz.setDifficulty(quizDTO.getDifficulty()); + userRepository.findById(quizDTO.getCreatorId()) + .ifPresent(quiz::setCreator); - List<Question> questions = questionRepository.findAllById(quizDTO.getQuestionIds()); - quiz.setQuestions(questions); - - return quizRepository.save(quiz); + quizRepository.save(quiz); + return convertToQuizDTO(quiz); } + @Transactional public QuizDTO updateQuiz(QuizDTO quizDTO) { - Quiz quiz = quizRepository.findById(quizDTO.getId()) - .orElseThrow(() -> new QuizNotFoundException("Quiz not found with id " + quizDTO.getId())); + if (quizDTO.getId() == null) { + throw new IllegalArgumentException("Quiz ID must be provided for update."); + } + + Optional<Quiz> existingQuiz = quizRepository.findById(quizDTO.getId()); + if (existingQuiz.isPresent()) { + Quiz quiz = existingQuiz.get(); + quiz.setTitle(quizDTO.getTitle()); + quiz.setCategory(quizDTO.getCategory()); + quiz.setDifficulty(quizDTO.getDifficulty()); + + Quiz updatedQuiz = quizRepository.save(quiz); - quiz.setTitle(quizDTO.getTitle()); - quiz.setCategory(quizDTO.getCategory()); - Quiz updatedQuiz = quizRepository.save(quiz); - return convertToQuizDTO(updatedQuiz); + + return convertToQuizDTO(updatedQuiz); + } else { + throw new QuizNotFoundException("Quiz with ID " + quizDTO.getId() + " not found."); + } } public void deleteQuiz(Integer id) { Quiz quiz = quizRepository.findById(id) .orElseThrow(() -> new QuizNotFoundException("Quiz not found with id " + id)); - questionRepository.deleteAll(quiz.getQuestions()); + questionRepository.deleteAll(questionRepository.findAllByQuizId(id)); quizRepository.delete(quiz); } @@ -85,11 +101,8 @@ public class QuizService { quizDTO.setId(quiz.getId()); quizDTO.setTitle(quiz.getTitle()); quizDTO.setCategory(quiz.getCategory()); - - List<Integer> questionIds = quiz.getQuestions().stream() - .map(Question::getId) - .collect(Collectors.toList()); - quizDTO.setQuestionIds(questionIds); + quizDTO.setDifficulty(quiz.getDifficulty()); + quizDTO.setCreatorId(quiz.getCreator().getId()); return quizDTO; } diff --git a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/TokenService.java b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/TokenService.java index be5e5b81647a195be18af6fd026c45e1d5f02480..92dc64f5cfc21dde7e1fbf6a598f233dc1e92482 100644 --- a/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/TokenService.java +++ b/FullstackProsjekt/src/backend/main/java/edu/ntnu/idatt2105/service/TokenService.java @@ -1,11 +1,15 @@ package edu.ntnu.idatt2105.service; +import edu.ntnu.idatt2105.dto.UserDTO; +import edu.ntnu.idatt2105.model.User; +import edu.ntnu.idatt2105.repository.UserRepository; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.oauth2.jwt.*; import org.springframework.stereotype.Service; import java.time.Instant; +import java.util.Optional; import java.util.stream.Collectors; @Service @@ -13,10 +17,12 @@ public class TokenService { private final JwtEncoder jwtEncoder; private final JwtDecoder jwtDecoder; + private final UserRepository userRepository; - public TokenService(JwtEncoder jwtEncoder, JwtDecoder jwtDecoder) { + public TokenService(JwtEncoder jwtEncoder, JwtDecoder jwtDecoder, UserRepository userRepository) { this.jwtEncoder = jwtEncoder; this.jwtDecoder = jwtDecoder; + this.userRepository = userRepository; } public String generateJwt(Authentication auth) { @@ -50,5 +56,20 @@ public class TokenService { Jwt jwt = jwtDecoder.decode(token); return jwt.getSubject(); } + + public UserDTO getUserDTOFromToken(String token) { + UserDTO userDTO = new UserDTO(); + Jwt jwt = jwtDecoder.decode(token); + if (Optional.ofNullable(jwt.getSubject()).isEmpty()) { + throw new IllegalArgumentException("Invalid token, could not decode"); + } else { + User user = userRepository.findByUsername(jwt.getSubject()) + .orElseThrow(() -> new IllegalArgumentException("User not found")); + userDTO.setId(user.getId()); + userDTO.setUsername(user.getUsername()); + userDTO.setPassword(user.getPassword()); + } + return userDTO; + } } diff --git a/FullstackProsjekt/src/backend/main/resources/application.properties b/FullstackProsjekt/src/backend/main/resources/application.properties index 1ecdc67e36103bae075238695f70cca336ea35ad..90c60c6487d77ad8d9fa8bfaf68c6bb5360965d5 100644 --- a/FullstackProsjekt/src/backend/main/resources/application.properties +++ b/FullstackProsjekt/src/backend/main/resources/application.properties @@ -1,5 +1,4 @@ spring.application.name=FullstackProsjekt -server.port = 5173 spring.datasource.url=jdbc:h2:tcp://129.241.98.27:9092/~/brainstormer_db spring.datasource.driverClassName=org.h2.Driver diff --git a/FullstackProsjekt/src/frontend/src/api.js b/FullstackProsjekt/src/frontend/src/api.js index 17162e5cfcde00f368383acc885d206b4ecdb69d..99fcfddd722f411deba4aa403af4ddef38902548 100644 --- a/FullstackProsjekt/src/frontend/src/api.js +++ b/FullstackProsjekt/src/frontend/src/api.js @@ -1,6 +1,6 @@ import axios from 'axios'; export const apiClient = axios.create({ - baseURL: 'http://localhost:5173/api', + baseURL: 'http://localhost:8080/api', //TODO: set api URL }); \ No newline at end of file diff --git a/FullstackProsjekt/src/frontend/src/tokenController.js b/FullstackProsjekt/src/frontend/src/tokenController.js index fcbdc07fb7d8cf05c56b9070b5339691c7b2819e..fb041de74f491e440b3db4ff63a00cd6f8431db1 100644 --- a/FullstackProsjekt/src/frontend/src/tokenController.js +++ b/FullstackProsjekt/src/frontend/src/tokenController.js @@ -12,7 +12,14 @@ export const removeToken = () => { localStorage.removeItem('token'); } -export const getIdByToken= () => { - //TODO: set up encryption and getID - return apiClient.get('/user/getId/' + getToken()); -} \ No newline at end of file +export const getIdByToken = async () => { + return new Promise((resolve, reject) => { + apiClient.get('/user/getId/' + getToken()) + .then(response => { + resolve(response.data.id); + }) + .catch(error => { + reject(error); + }); + }); +}; \ No newline at end of file diff --git a/FullstackProsjekt/src/frontend/src/views/LoginView.vue b/FullstackProsjekt/src/frontend/src/views/LoginView.vue index 59811937eb6c19c54053cce5f3d4f2bc3620ab78..0edf3e3a88afb91f466e22b8bf476fd12817705e 100644 --- a/FullstackProsjekt/src/frontend/src/views/LoginView.vue +++ b/FullstackProsjekt/src/frontend/src/views/LoginView.vue @@ -22,7 +22,7 @@ export default { username: this.username, password: this.password }).then(response => { - setToken(response.data.token); //TODO: check token name + setToken(response.data.jwt); //TODO: check token name }); } catch (error) { //TODO: proper error handling diff --git a/FullstackProsjekt/src/frontend/src/views/NewQuizView.vue b/FullstackProsjekt/src/frontend/src/views/NewQuizView.vue index ae1180c2622618046ed1410fac8571e6fcdc6a3c..71d971370489da54de7d4627fdd65cdde3ba214e 100644 --- a/FullstackProsjekt/src/frontend/src/views/NewQuizView.vue +++ b/FullstackProsjekt/src/frontend/src/views/NewQuizView.vue @@ -17,7 +17,6 @@ export default { quiz: null, quizId: null, quizTitle: '', - questions: [], category: '', difficulty: '', errorMsg: '', @@ -34,13 +33,13 @@ export default { methods: { async constructQuiz() { try { - await apiClient.post('quiz/create', { + const post = { title: this.quizTitle, - questionIds: this.questions, creatorId: this.creatorId, category: this.selectedCategory, difficulty: this.selectedDifficulty - }).then(response => { + } + await apiClient.post('quiz/create', post).then(response => { this.quizId = JSON.parse(response.data.id); router.push({name: 'editQuiz', params: {quizId: this.quizId}}); }) @@ -48,8 +47,8 @@ export default { this.errorMsg = 'Cannot construct quiz'; } }, - getUser() { - this.creatorId = getIdByToken(); + async getUser() { + this.creatorId = await getIdByToken(); } }, } @@ -57,30 +56,50 @@ export default { <template> <body> - <div class="new-quiz-page"> - <form @submit.prevent="constructQuiz"> - <router-link to="/overviewQuiz"> <- </router-link> - <h1>New quiz</h1> - <div> - <h2>Title</h2> - <input> - </div> - <div> - <h2>Category</h2> - <form> - <select v-model="selectedCategory" > - <option v-for="category in categories" :key="category.id" :value="category">{{categories.category}}</option> - </select> - </form> - </div> - <div> - <h2>Difficulty</h2> - <form> - <select v-model="selectedDifficulty"> - <option v-for="difficulty in difficulties" :key="difficulty.id" :value="difficulty">{{difficulties.difficulty}}</option> - </select> - </form> - </div> + <form @submit.prevent="constructQuiz"> + <div class="newQuizDiv"> + <router-link to="/overviewQuiz"> <- </router-link> + <h1>New quiz</h1> + <div> + <h2>Title</h2> + <input type="text" required v-model="quizTitle" placeholder="Insert title here..."/> <br> + </div> + <div> + <h2>Category</h2> + <form> + <select v-model="selectedCategory"> + <option v-for="category in categories" :key="category" :value="category">{{ category }}</option> + </select> + </form> + </div> + <div> + <h2>Difficulty</h2> + <form> + <select v-model="selectedDifficulty"> + <option v-for="difficulty in difficulties" :key="difficulty" :value="difficulty">{{ difficulty}}</option> + </select> + </form> + </div> + <!-- + <div class="question-table"> + <table class="table"> + <thead> + <tr> + <th scope="col">#</th> + <th scope="col">Question</th> + <th scope="col">Action</th> + </tr> + </thead> + <tbody> + <tr> + <th scope="row">1</th> + <td>What is Vue?</td> + <td> + <button class="play-btn">View</button> + <button class="edit-btn">Edit</button> + <button class="delete-btn"> Delete</button> + </td> + </tr> <div class="footer"> <router-link to="/overviewQuiz" class="delete-btn"> Cancel </router-link> diff --git a/FullstackProsjekt/src/frontend/src/views/OverviewQuizView.vue b/FullstackProsjekt/src/frontend/src/views/OverviewQuizView.vue index 317cac17951cfe076cfe7871f31ff639c0bf8afa..b5af25c5c0a9ca9fae11051e27cfc71c1af23962 100644 --- a/FullstackProsjekt/src/frontend/src/views/OverviewQuizView.vue +++ b/FullstackProsjekt/src/frontend/src/views/OverviewQuizView.vue @@ -114,34 +114,25 @@ export default { }; }, mounted() { - this.setUserId(); - this.populateQuizzes(); + this.populateQuizzes(); // Call populateQuizzes directly }, - methods: { + methods: { async populateQuizzes() { try { - await apiClient.get('/quiz/creator/' + this.userId).then(response => { - this.quizList = JSON.parse(JSON.stringify(response.data)); - //TODO: set max amt. - this.quizNo = this.quizList.length; - }); + await this.setUserId(); // Wait for setUserId to complete before fetching quizzes + const response = await apiClient.get('/quiz/creator/' + this.userId); + this.quizList = response.data; + this.quizNo = this.quizList.length; } catch (error) { - //TODO: proper error handling + // Handle errors this.errorMsg = 'Error retrieving quizzes'; } - },/* - populateQuizzes() { - this.quizNo = this.quizList.length; - },*/ - setUserId() { - this.userId = getIdByToken(); + }, + async setUserId() { + this.userId = await getIdByToken(); // Wait for getIdByToken() to resolve before assigning to this.userId } - }, - created() { - this.setUserId(); - this.populateQuizzes(); - } + } } </script> diff --git a/FullstackProsjekt/src/frontend/src/views/SignupView.vue b/FullstackProsjekt/src/frontend/src/views/SignupView.vue index 0908f8ed5c709824be12e01dbd85e7a83072dc68..6137c56644c4815531472b6206a3e9b77c51d88c 100644 --- a/FullstackProsjekt/src/frontend/src/views/SignupView.vue +++ b/FullstackProsjekt/src/frontend/src/views/SignupView.vue @@ -21,7 +21,6 @@ await apiClient.post('/auth/register', { username: this.username, password: this.password, - password_confirm: this.password_confirm }).then(response => { //TODO: display successful registration to user alert("User: " + this.username + " created!") diff --git a/FullstackProsjekt/src/frontend/vite.config.js b/FullstackProsjekt/src/frontend/vite.config.js index be87da7266d27896255b5297f4d5c2e45e51d327..ba647c09b6b4db1abc0da950157a07eb5767e4df 100644 --- a/FullstackProsjekt/src/frontend/vite.config.js +++ b/FullstackProsjekt/src/frontend/vite.config.js @@ -8,7 +8,7 @@ import svgLoader from 'vite-svg-loader' // https://vitejs.dev/config/ export default defineConfig({ server: { - port: 4173 + port: 5173 }, plugins: [ vue(),