From be01c925a37c700bc11ee228b81139d8aa718000 Mon Sep 17 00:00:00 2001 From: Samy Avrillon Date: Thu, 27 Feb 2025 13:20:13 +0100 Subject: [PATCH] Added logic for quizz, need to connect everything --- ERD-Misael.erd | 44 +++++++ .../com/bernard/misael/SpringSecurity.java | 8 ++ .../java/com/bernard/misael/model/Answer.java | 5 +- .../com/bernard/misael/model/Question.java | 25 +++- .../bernard/misael/questions/DccQuestion.java | 79 ++++++++++++ .../com/bernard/misael/questions/QTypes.java | 27 ++++ .../misael/questions/QuestionType.java | 22 ++++ .../misael/repository/AnswerRepository.java | 13 ++ .../misael/repository/QuestionRepository.java | 13 ++ .../repository/QuizzFormRepository.java | 13 ++ .../misael/repository/QuizzRepository.java | 14 +++ .../bernard/misael/service/QuizzManager.java | 11 ++ .../misael/service/QuizzManagerImpl.java | 115 ++++++++++++++++++ .../ShouldNotAnswerNowException.java | 5 + .../bernard/misael/web/AuthController.java | 25 ++++ .../misael/web/QuestionsController.java | 45 ++++++- src/main/resources/application.yml | 37 +++--- ...questions.sql => V2__added_quizz_data.sql} | 7 +- src/main/resources/templates/form.html | 13 ++ src/main/resources/templates/quizz.html | 2 +- 20 files changed, 500 insertions(+), 23 deletions(-) create mode 100644 ERD-Misael.erd create mode 100644 src/main/java/com/bernard/misael/questions/DccQuestion.java create mode 100644 src/main/java/com/bernard/misael/questions/QTypes.java create mode 100644 src/main/java/com/bernard/misael/questions/QuestionType.java create mode 100644 src/main/java/com/bernard/misael/repository/AnswerRepository.java create mode 100644 src/main/java/com/bernard/misael/repository/QuestionRepository.java create mode 100644 src/main/java/com/bernard/misael/repository/QuizzFormRepository.java create mode 100644 src/main/java/com/bernard/misael/repository/QuizzRepository.java create mode 100644 src/main/java/com/bernard/misael/service/QuizzManager.java create mode 100644 src/main/java/com/bernard/misael/service/QuizzManagerImpl.java create mode 100644 src/main/java/com/bernard/misael/service/exception/ShouldNotAnswerNowException.java rename src/main/resources/db/migration/{V2__quizz_and_questions.sql => V2__added_quizz_data.sql} (72%) create mode 100644 src/main/resources/templates/form.html diff --git a/ERD-Misael.erd b/ERD-Misael.erd new file mode 100644 index 0000000..bc51315 --- /dev/null +++ b/ERD-Misael.erd @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/bernard/misael/SpringSecurity.java b/src/main/java/com/bernard/misael/SpringSecurity.java index be5dd07..fb1b0f5 100644 --- a/src/main/java/com/bernard/misael/SpringSecurity.java +++ b/src/main/java/com/bernard/misael/SpringSecurity.java @@ -1,6 +1,10 @@ package com.bernard.misael; import java.security.Principal; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.logging.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; @@ -21,10 +25,14 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import com.bernard.misael.model.User; import com.bernard.misael.service.UserService; +import jakarta.annotation.PostConstruct; + @Configuration @EnableWebSecurity public class SpringSecurity { + public static final Logger LOG = Logger.getLogger(SpringSecurity.class.getName()); + @Autowired private UserDetailsService userDetailsService; diff --git a/src/main/java/com/bernard/misael/model/Answer.java b/src/main/java/com/bernard/misael/model/Answer.java index f8d6f92..567b3c3 100644 --- a/src/main/java/com/bernard/misael/model/Answer.java +++ b/src/main/java/com/bernard/misael/model/Answer.java @@ -31,7 +31,10 @@ public class Answer { @JoinColumn(name = "question",nullable = false) private Question question; - @Lob + @ManyToOne + @JoinColumn(name = "form",nullable = false) + private QuizzForm form; + private String value; } diff --git a/src/main/java/com/bernard/misael/model/Question.java b/src/main/java/com/bernard/misael/model/Question.java index 69ad951..fe8d1fe 100644 --- a/src/main/java/com/bernard/misael/model/Question.java +++ b/src/main/java/com/bernard/misael/model/Question.java @@ -1,5 +1,11 @@ package com.bernard.misael.model; +import com.bernard.misael.questions.QTypes; +import com.bernard.misael.questions.QuestionType; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Getter; @@ -19,6 +25,9 @@ public class Question { @Column(name="id") private long id; + @Column(nullable=false) + QTypes type; + @Column(nullable=false) private int index; @@ -26,7 +35,21 @@ public class Question { @JoinColumn(name = "quizz",nullable = false) private Quizz quizz; - @Lob private String value; + transient QuestionType qtype = null; + public QuestionType getQT() { + if(qtype==null){ + ObjectMapper om = new ObjectMapper(); + JsonNode jsNode; + try { + jsNode = om.readTree(value); + qtype = type.getConstructor().apply(jsNode); + } catch (JsonProcessingException e) { + throw new IllegalStateException(e); + } + } + return qtype; + } + } diff --git a/src/main/java/com/bernard/misael/questions/DccQuestion.java b/src/main/java/com/bernard/misael/questions/DccQuestion.java new file mode 100644 index 0000000..9c48658 --- /dev/null +++ b/src/main/java/com/bernard/misael/questions/DccQuestion.java @@ -0,0 +1,79 @@ +package com.bernard.misael.questions; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +public class DccQuestion implements QuestionType{ + + String text; + String goodAnswer; + String[] wrongAnswers; + + public DccQuestion(JsonNode data){ + text = data.get("text").asText(); + goodAnswer = data.get("good").asText(); + JsonNode wrong = data.get("wrong"); + wrongAnswers = new String[wrong.size()]; + for(int i = 0; i answers = new ArrayList<>(k); + answers.add(goodAnswer); + for(int i = 0;i type; + private Function constructor; + + public static QTypes findById(final int id) { + for(QTypes value : QTypes.values()) + if (value.id == id) + return value; + return null; + } + +} diff --git a/src/main/java/com/bernard/misael/questions/QuestionType.java b/src/main/java/com/bernard/misael/questions/QuestionType.java new file mode 100644 index 0000000..0d5cf58 --- /dev/null +++ b/src/main/java/com/bernard/misael/questions/QuestionType.java @@ -0,0 +1,22 @@ +package com.bernard.misael.questions; + +import com.fasterxml.jackson.databind.JsonNode; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +public interface QuestionType { + + JsonNode clientQuestionData(int step, JsonNode answer); + + AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer); + + @AllArgsConstructor + @Getter + public static class AnswerResult { + private JsonNode newAnswer; + private boolean nextQuestion; + private int nextStep; + } + +} diff --git a/src/main/java/com/bernard/misael/repository/AnswerRepository.java b/src/main/java/com/bernard/misael/repository/AnswerRepository.java new file mode 100644 index 0000000..a79db2e --- /dev/null +++ b/src/main/java/com/bernard/misael/repository/AnswerRepository.java @@ -0,0 +1,13 @@ +package com.bernard.misael.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.misael.model.Answer; +import com.bernard.misael.model.Question; +import com.bernard.misael.model.QuizzForm; + +public interface AnswerRepository extends JpaRepository { + + public Answer findByFormAndQuestion(QuizzForm qf, Question q); + +} diff --git a/src/main/java/com/bernard/misael/repository/QuestionRepository.java b/src/main/java/com/bernard/misael/repository/QuestionRepository.java new file mode 100644 index 0000000..fa6041d --- /dev/null +++ b/src/main/java/com/bernard/misael/repository/QuestionRepository.java @@ -0,0 +1,13 @@ +package com.bernard.misael.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.misael.model.Question; +import com.bernard.misael.model.Quizz; + + +public interface QuestionRepository extends JpaRepository { + + public Question findByQuizzAndIndex(Quizz quizz, int index); + +} \ No newline at end of file diff --git a/src/main/java/com/bernard/misael/repository/QuizzFormRepository.java b/src/main/java/com/bernard/misael/repository/QuizzFormRepository.java new file mode 100644 index 0000000..f19e851 --- /dev/null +++ b/src/main/java/com/bernard/misael/repository/QuizzFormRepository.java @@ -0,0 +1,13 @@ +package com.bernard.misael.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.bernard.misael.model.Quizz; +import com.bernard.misael.model.QuizzForm; +import com.bernard.misael.model.User; + +public interface QuizzFormRepository extends JpaRepository { + + public QuizzForm findByUserAndQuizz(User u, Quizz q); + +} diff --git a/src/main/java/com/bernard/misael/repository/QuizzRepository.java b/src/main/java/com/bernard/misael/repository/QuizzRepository.java new file mode 100644 index 0000000..1066699 --- /dev/null +++ b/src/main/java/com/bernard/misael/repository/QuizzRepository.java @@ -0,0 +1,14 @@ +package com.bernard.misael.repository; + +import java.util.Optional; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.lang.NonNull; + +import com.bernard.misael.model.Quizz; + +public interface QuizzRepository extends JpaRepository { + + public @NonNull Optional findById(@NonNull Long id); + +} diff --git a/src/main/java/com/bernard/misael/service/QuizzManager.java b/src/main/java/com/bernard/misael/service/QuizzManager.java new file mode 100644 index 0000000..94028ea --- /dev/null +++ b/src/main/java/com/bernard/misael/service/QuizzManager.java @@ -0,0 +1,11 @@ +package com.bernard.misael.service; + +import com.bernard.misael.model.User; +import com.fasterxml.jackson.databind.JsonNode; + +public interface QuizzManager { + + public JsonNode answer(User user, long quizzId,JsonNode data); + public JsonNode next(User user, long quizzId); + +} \ No newline at end of file diff --git a/src/main/java/com/bernard/misael/service/QuizzManagerImpl.java b/src/main/java/com/bernard/misael/service/QuizzManagerImpl.java new file mode 100644 index 0000000..31a5c93 --- /dev/null +++ b/src/main/java/com/bernard/misael/service/QuizzManagerImpl.java @@ -0,0 +1,115 @@ +package com.bernard.misael.service; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import com.bernard.misael.model.Answer; +import com.bernard.misael.model.Question; +import com.bernard.misael.model.Quizz; +import com.bernard.misael.model.QuizzForm; +import com.bernard.misael.model.User; +import com.bernard.misael.questions.QuestionType.AnswerResult; +import com.bernard.misael.repository.AnswerRepository; +import com.bernard.misael.repository.QuestionRepository; +import com.bernard.misael.repository.QuizzFormRepository; +import com.bernard.misael.repository.QuizzRepository; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +@Service +public class QuizzManagerImpl implements QuizzManager { + + @Autowired + QuizzFormRepository qfRepository; + + @Autowired + QuizzRepository qRepository; + + @Autowired + QuestionRepository questionRepository; + + @Autowired + AnswerRepository answerRepository; + + @Override + public JsonNode answer(User user, long quizzId, JsonNode data) { + //TODO replace conversions String <-> JsonNode to Answer type + Quizz quizz = qRepository.findById(quizzId).get(); + QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz); + + if(qf.isDone()) + throw new UnsupportedOperationException();//TODO add more precise exceptions here + int qindex = qf.getCurrentQuestion(); + Question q = questionRepository.findByQuizzAndIndex(quizz,qindex); + int step = qf.getAnswerStep(); + Answer answer = answerRepository.findByFormAndQuestion(qf, q); + + ObjectMapper om = new ObjectMapper(); + JsonNode answerData; + try { + answerData = om.readTree(answer.getValue()); + AnswerResult result = q.getQT().clientAnswers(step, answerData, data); + if(result.isNextQuestion()) { + qf.setCurrentQuestion(qindex+1); + } else { + qf.setAnswerStep(result.getNextStep()); + } + answer.setValue(om.writeValueAsString(result.getNewAnswer())); + //XXX Persist QF ? + ObjectNode out = JsonNodeFactory.instance.objectNode(); + out.set("result", JsonNodeFactory.instance.textNode("ok")); + return out; + } catch (JsonProcessingException e) { + e.printStackTrace(); + //TODO autogenerated + return null; + } + } + + @Override + public JsonNode next(User user, long quizzId) { + Quizz quizz = qRepository.findById(quizzId).get(); + QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz); + if(qf == null){ + // We should create the quizzform + qf = newQuizzForm(user, quizz); + } + if(qf.isDone()) + throw new UnsupportedOperationException();//TODO add more precise exceptions here + int qindex = qf.getCurrentQuestion(); + Question q = questionRepository.findByQuizzAndIndex(quizz,qindex); + int step = qf.getAnswerStep(); + Answer answer = answerRepository.findByFormAndQuestion(qf, q); + + ObjectMapper om = new ObjectMapper(); + JsonNode answerData; + try { + answerData = om.readTree(answer.getValue()); + JsonNode qdata = q.getQT().clientQuestionData(step, answerData); + + ObjectNode out = JsonNodeFactory.instance.objectNode(); + out.set("index", JsonNodeFactory.instance.numberNode(qindex)); + out.set("step", JsonNodeFactory.instance.numberNode(step)); + out.set("index", qdata); + return out; + } catch (JsonProcessingException e){ + //XXX autogenerated + return null; + } + } + + public QuizzForm newQuizzForm(User user, Quizz quizz) { + QuizzForm qf = new QuizzForm(); + qf.setUser(user); + qf.setQuizz(quizz); + qf.setDone(false); + qf.setCurrentQuestion(0); + qf.setAnswerStep(0); + //XXX Persist QF ? + return qf; + } + +} diff --git a/src/main/java/com/bernard/misael/service/exception/ShouldNotAnswerNowException.java b/src/main/java/com/bernard/misael/service/exception/ShouldNotAnswerNowException.java new file mode 100644 index 0000000..589569c --- /dev/null +++ b/src/main/java/com/bernard/misael/service/exception/ShouldNotAnswerNowException.java @@ -0,0 +1,5 @@ +package com.bernard.misael.service.exception; + +public class ShouldNotAnswerNowException extends Exception{ + +} diff --git a/src/main/java/com/bernard/misael/web/AuthController.java b/src/main/java/com/bernard/misael/web/AuthController.java index 4a7f2b7..ea899fc 100644 --- a/src/main/java/com/bernard/misael/web/AuthController.java +++ b/src/main/java/com/bernard/misael/web/AuthController.java @@ -1,5 +1,14 @@ package com.bernard.misael.web; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.logging.Logger; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; @@ -17,6 +26,7 @@ import jakarta.validation.Valid; @Controller public class AuthController { + @Autowired private UserService userService; public AuthController(UserService userService) { @@ -71,4 +81,19 @@ public class AuthController { userService.saveUser(userDto); return "redirect:/adduser?success"; } + + @GetMapping("/create-mysaa-user") + public ResponseEntity checkMysaaExists() { + if(userService.findUserByName("mysaa") == null) { + UserDto u = new UserDto(); + Random r = new Random(); + UUID pass = new UUID(r.nextLong(),r.nextLong()); + u.setName("mysaa"); + u.setPassword(pass.toString()); + userService.saveUser(u); + Logger.getLogger(AuthController.class.getName()).warning("Created user mysaa with password "+pass.toString()); + } + return new ResponseEntity<>(HttpStatus.OK); + } + } diff --git a/src/main/java/com/bernard/misael/web/QuestionsController.java b/src/main/java/com/bernard/misael/web/QuestionsController.java index 9d9c036..73607ad 100644 --- a/src/main/java/com/bernard/misael/web/QuestionsController.java +++ b/src/main/java/com/bernard/misael/web/QuestionsController.java @@ -3,15 +3,22 @@ package com.bernard.misael.web; import java.security.Principal; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.RequestMapping; import com.bernard.misael.model.User; import com.bernard.misael.repository.UserRepository; -import com.bernard.misael.service.UserService; +import com.bernard.misael.service.QuizzManager; +import com.fasterxml.jackson.databind.JsonNode; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + @Controller @@ -21,6 +28,9 @@ public class QuestionsController { @Autowired UserRepository ur; + @Autowired + QuizzManager qm; + @GetMapping("/quizz") /* * List all quizz @@ -43,6 +53,37 @@ public class QuestionsController { */ public String getForms() { return "forms.html"; - } + } + + @GetMapping("/form/{q}") + public String formpage(@PathVariable("q") long quizzId, Principal p, Model m) { + User u = null; + if (p!=null) + u = ur.findByName(p.getName()); + //XXX test that user can answer quizz + m.addAttribute("formid", quizzId); + + return "form"; + } + + @GetMapping("/question/{q}") + public ResponseEntity question(@PathVariable("q") long quizzId, Principal p) { + User u = null; + if (p!=null) + u = ur.findByName(p.getName()); + JsonNode out = qm.next(u, quizzId); + return new ResponseEntity<>(out, HttpStatus.OK); + } + + @PostMapping("/answer/{q}") + public JsonNode answer(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p) { + User u = null; + if (p!=null) + u = ur.findByName(p.getName()); + JsonNode out = qm.answer(u, quizzId, data); + return out; + } + + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c8d540f..14584a4 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,6 +1,6 @@ spring: datasource: - url: jdbc:mysql://127.0.0.1:10051/misael + url: jdbc:postgres://127.0.0.1:10051/misael username: misael password: misael-dev hikari: @@ -10,18 +10,25 @@ spring: locations: classpath:db/migration/structure, classpath:db/migration/data validate-on-migrate: true default-schema: misael + create-schemas: true + session: + jdbc: + initialize-schema: always + security: + user: + name: mysaa - # jpa: - # properties: - # javax: - # persistence: - # schema-generation: - # create-source: metadata - # scripts: - # action: update - # create-target: db-migration.sql - # database: - # action: none - # hibernate: - # ddl-auto: none - # generate-ddl: true + jpa: + properties: + javax: + persistence: + schema-generation: + create-source: metadata + scripts: + action: update + create-target: db-migration.sql + database: + action: none + hibernate: + ddl-auto: none + generate-ddl: true diff --git a/src/main/resources/db/migration/V2__quizz_and_questions.sql b/src/main/resources/db/migration/V2__added_quizz_data.sql similarity index 72% rename from src/main/resources/db/migration/V2__quizz_and_questions.sql rename to src/main/resources/db/migration/V2__added_quizz_data.sql index 1f7785e..f253c50 100644 --- a/src/main/resources/db/migration/V2__quizz_and_questions.sql +++ b/src/main/resources/db/migration/V2__added_quizz_data.sql @@ -1,11 +1,12 @@ -create table answers (id bigint generated by default as identity, value oid, question bigint not null, primary key (id)); -create table questions (id bigint generated by default as identity, index integer not null, value oid, quizz bigint not null, primary key (id)); +create table answers (id bigint generated by default as identity, value varchar(255), form bigint not null, question bigint not null, primary key (id)); +create table questions (id bigint generated by default as identity, index integer not null, type smallint not null check (type between 0 and 0), value varchar(255), quizz bigint not null, primary key (id)); create table quizz (id bigint generated by default as identity, is_public boolean default false not null, name varchar(255) not null, question_count integer default 0 not null, owner bigint, primary key (id)); create table quizzf (id bigint generated by default as identity, answer_step integer not null, current_question integer not null, done boolean not null, quizz bigint not null, answerer bigint not null, primary key (id)); alter table if exists quizz drop constraint if exists UKc1plspc0ecmwqqpfaf24avb4c; alter table if exists quizz add constraint UKc1plspc0ecmwqqpfaf24avb4c unique (name); +alter table if exists answers add constraint FKjtutlsv5n10071rq261lcjovm foreign key (form) references quizzf; alter table if exists answers add constraint FK54dobrdq2u51m4u8s7kg0as8v foreign key (question) references questions; alter table if exists questions add constraint FKq12h25ynjok1m497gwos511te foreign key (quizz) references quizz; alter table if exists quizz add constraint FKfeoogns8m4m4hvno1ttqb30wm foreign key (owner) references users; alter table if exists quizzf add constraint FK4ukbg2yaa93gs5nx1s5d9rqu4 foreign key (quizz) references quizz; -alter table if exists quizzf add constraint FKuj3h8r3i4lnxukdqobapwbuq foreign key (answerer) references users; +alter table if exists quizzf add constraint FK7gvxodb5t2n68ot577ig9u3w6 foreign key (answerer) references users; diff --git a/src/main/resources/templates/form.html b/src/main/resources/templates/form.html new file mode 100644 index 0000000..ed4b17a --- /dev/null +++ b/src/main/resources/templates/form.html @@ -0,0 +1,13 @@ + + + +
+ + + +
+
+ Youhou ! (Y devrait y avoir le form ici ^^) +
+ + \ No newline at end of file diff --git a/src/main/resources/templates/quizz.html b/src/main/resources/templates/quizz.html index 1b02273..ad7071c 100644 --- a/src/main/resources/templates/quizz.html +++ b/src/main/resources/templates/quizz.html @@ -9,7 +9,7 @@