Added better error messages, and tested json API
This commit is contained in:
parent
be01c925a3
commit
3a7a413c69
@ -40,12 +40,16 @@ dependencies {
|
||||
implementation 'org.flywaydb:flyway-database-postgresql:11.3.2'
|
||||
implementation 'org.springframework.session:spring-session-jdbc'
|
||||
implementation 'jakarta.validation:jakarta.validation-api:3.1.1'
|
||||
runtimeOnly 'org.webjars:jquery:1.9.1'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-devtools'
|
||||
developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
|
||||
runtimeOnly 'org.postgresql:postgresql'
|
||||
testImplementation 'org.springframework.boot:spring-boot-starter-test'
|
||||
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
|
||||
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:2.18.2'
|
||||
|
||||
//Lombok
|
||||
compileOnly 'org.projectlombok:lombok:1.18.36'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.36'
|
||||
|
||||
@ -1,6 +1,12 @@
|
||||
package com.bernard.misael.model;
|
||||
|
||||
import com.bernard.misael.service.JsonNodeConverter;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Convert;
|
||||
import jakarta.persistence.Converter;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
@ -35,6 +41,14 @@ public class Answer {
|
||||
@JoinColumn(name = "form",nullable = false)
|
||||
private QuizzForm form;
|
||||
|
||||
private String value;
|
||||
@Convert(converter = JsonNodeConverter.class)
|
||||
private JsonNode value;
|
||||
|
||||
public Answer(QuizzForm qf, Question q) {
|
||||
super();
|
||||
this.setQuestion(q);
|
||||
this.setForm(qf);
|
||||
this.setValue(JsonNodeFactory.instance.objectNode());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -6,6 +6,9 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.bernard.misael.service.exception.MalformedAnswerException;
|
||||
import com.bernard.misael.service.exception.MalformedClientAnswerException;
|
||||
import com.bernard.misael.service.exception.QuestionTypeException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
@ -26,7 +29,7 @@ public class DccQuestion implements QuestionType{
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode clientQuestionData(int step, JsonNode answer) {
|
||||
public JsonNode clientQuestionData(int step, JsonNode answer) throws QuestionTypeException{
|
||||
ObjectNode o = JsonNodeFactory.instance.objectNode();
|
||||
switch (step) {
|
||||
case 0:
|
||||
@ -34,6 +37,7 @@ public class DccQuestion implements QuestionType{
|
||||
return o;
|
||||
case 1:
|
||||
Random r = new Random();
|
||||
if(!answer.has("type") || !answer.get("type").isInt()) throw new MalformedAnswerException();
|
||||
int k = answer.get("type").asInt();
|
||||
switch(k) {
|
||||
case 0:
|
||||
@ -42,13 +46,13 @@ public class DccQuestion implements QuestionType{
|
||||
case 4:
|
||||
List<String> answers = new ArrayList<>(k);
|
||||
answers.add(goodAnswer);
|
||||
for(int i = 0;i<k;i++)
|
||||
for(int i = 0;i<k-1;i++)
|
||||
answers.add(wrongAnswers[i]);
|
||||
Collections.shuffle(answers, r);
|
||||
o.putArray("answers").addAll(answers.stream().map(JsonNodeFactory.instance::textNode).collect(Collectors.toList()));
|
||||
return o;
|
||||
default:
|
||||
throw new IllegalStateException("Invalid Answer type");
|
||||
throw new MalformedAnswerException();
|
||||
}
|
||||
|
||||
default:
|
||||
@ -57,16 +61,18 @@ public class DccQuestion implements QuestionType{
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer) {
|
||||
public AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer) throws QuestionTypeException {
|
||||
ObjectNode o = JsonNodeFactory.instance.objectNode();
|
||||
switch (step) {
|
||||
case 0:
|
||||
if(!clientAnswer.isInt()) throw new MalformedClientAnswerException();
|
||||
int answerType = clientAnswer.asInt();
|
||||
if(answerType != 0 && answerType != 2 && answerType != 4)
|
||||
throw new IllegalArgumentException("Illegal answer type: "+answerType);
|
||||
o.put("type", answerType);
|
||||
return new AnswerResult(o, false, 1);
|
||||
case 1:
|
||||
if(!clientAnswer.isTextual()) throw new MalformedClientAnswerException();
|
||||
String answer = clientAnswer.asText();
|
||||
o = oldAnswer.deepCopy();
|
||||
o.put("answer", answer);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.bernard.misael.questions;
|
||||
|
||||
import com.bernard.misael.service.exception.QuestionTypeException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
@ -7,9 +8,9 @@ import lombok.Getter;
|
||||
|
||||
public interface QuestionType {
|
||||
|
||||
JsonNode clientQuestionData(int step, JsonNode answer);
|
||||
JsonNode clientQuestionData(int step, JsonNode answer) throws QuestionTypeException;
|
||||
|
||||
AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer);
|
||||
AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer) throws QuestionTypeException;
|
||||
|
||||
@AllArgsConstructor
|
||||
@Getter
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package com.bernard.misael.service;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.persistence.AttributeConverter;
|
||||
|
||||
|
||||
public class JsonNodeConverter implements AttributeConverter<JsonNode, String> {
|
||||
|
||||
private ObjectMapper om;
|
||||
|
||||
public JsonNodeConverter() {
|
||||
this.om = new ObjectMapper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertToDatabaseColumn(JsonNode attribute) {
|
||||
return attribute.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode convertToEntityAttribute(String dbData) {
|
||||
try {
|
||||
return om.readTree(dbData);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalArgumentException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bernard.misael.service;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -13,9 +15,10 @@ 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.bernard.misael.service.exception.MalformedAnswerException;
|
||||
import com.bernard.misael.service.exception.MalformedClientAnswerException;
|
||||
import com.bernard.misael.service.exception.QuestionTypeException;
|
||||
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;
|
||||
|
||||
@ -36,69 +39,115 @@ public class QuizzManagerImpl implements QuizzManager {
|
||||
|
||||
@Override
|
||||
public JsonNode answer(User user, long quizzId, JsonNode data) {
|
||||
//TODO replace conversions String <-> JsonNode to Answer type
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
if(!data.has("index") || !data.get("index").isInt())
|
||||
return errorNode("Request should contain the question index");
|
||||
if(!data.has("step") || !data.get("step").isInt())
|
||||
return errorNode("Request should contain the answer step");
|
||||
if(!data.has("data"))
|
||||
return errorNode("Request should contain the answer data");
|
||||
if(user == null)
|
||||
return errorNode("You must be logged in to answer");
|
||||
Optional<Quizz> oquizz = qRepository.findById(quizzId);
|
||||
if(!oquizz.isPresent())
|
||||
return errorNode("Could not find the quizz with id "+quizzId);
|
||||
Quizz quizz = oquizz.get();
|
||||
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
|
||||
|
||||
if(qf == null)
|
||||
return errorNode("The quizzform does not exist, ask the question first");
|
||||
if(qf.isDone())
|
||||
throw new UnsupportedOperationException();//TODO add more precise exceptions here
|
||||
return errorNode("You're done with the quizz, you cannot answer anymore");
|
||||
int qindex = qf.getCurrentQuestion();
|
||||
if(qindex != data.get("index").intValue())
|
||||
return errorNode("You are not answering the right question (you answer question "+data.get("index").intValue()
|
||||
+" where you should answer question "+qindex+")");
|
||||
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
|
||||
if(q == null)
|
||||
return errorNode("Could not find question "+qindex);
|
||||
int step = qf.getAnswerStep();
|
||||
if(step != data.get("step").intValue())
|
||||
return errorNode("You are not answering the right step of the question (you answer step "+data.get("step").intValue()
|
||||
+" where you should step question "+step+")");
|
||||
|
||||
Answer answer = answerRepository.findByFormAndQuestion(qf, q);
|
||||
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
JsonNode answerData;
|
||||
if(answer == null)
|
||||
return errorNode("The database answer object does not exist, ask the question first");
|
||||
|
||||
JsonNode answerData = answer.getValue();
|
||||
AnswerResult result;
|
||||
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;
|
||||
result = q.getQT().clientAnswers(step, answerData, data.get("data"));
|
||||
} catch (MalformedAnswerException e) {
|
||||
return errorNode("The previous answer stored in database is invalid");
|
||||
} catch (MalformedClientAnswerException e) {
|
||||
return errorNode("This answer is not valid here");
|
||||
} catch (QuestionTypeException e) {
|
||||
return errorNode("Unknown error from the QuestionType");
|
||||
} catch (IllegalArgumentException e) {
|
||||
return errorNode("The QuestionType did not recognize the step of the question");
|
||||
}
|
||||
if(result.isNextQuestion()) {
|
||||
qf.setCurrentQuestion(qindex+1);
|
||||
qf.setAnswerStep(0);
|
||||
if(qf.getCurrentQuestion() >= quizz.getQuestionCount()){
|
||||
// The quizz is done
|
||||
qf.setDone(true);
|
||||
}
|
||||
} else {
|
||||
qf.setAnswerStep(result.getNextStep());
|
||||
}
|
||||
answer.setValue(result.getNewAnswer());
|
||||
answerRepository.save(answer);
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode next(User user, long quizzId) {
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
if(user == null)
|
||||
return errorNode("You need to be logged in to discover the questions");
|
||||
Optional<Quizz> oquizz = qRepository.findById(quizzId);
|
||||
if(!oquizz.isPresent())
|
||||
return errorNode("Could not find quizz with id "+quizzId);
|
||||
Quizz quizz = oquizz.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
|
||||
return errorNode("No more questions");
|
||||
int qindex = qf.getCurrentQuestion();
|
||||
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
|
||||
if(q == null)
|
||||
return errorNode("Could not find question "+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;
|
||||
if(answer==null){
|
||||
// We construct the blank answer
|
||||
answer = new Answer(qf,q);
|
||||
answerRepository.save(answer);
|
||||
}
|
||||
|
||||
JsonNode answerData;
|
||||
answerData = answer.getValue();
|
||||
JsonNode qdata;
|
||||
try {
|
||||
qdata = q.getQT().clientQuestionData(step, answerData);
|
||||
} catch (MalformedAnswerException e) {
|
||||
return errorNode("The previous answer stored in database is invalid");
|
||||
} catch (QuestionTypeException e) {
|
||||
return errorNode("Unknown error from the QuestionType");
|
||||
} catch (IllegalArgumentException e) {
|
||||
return errorNode("The QuestionType did not recognize the step of the question");
|
||||
}
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
out.set("index", JsonNodeFactory.instance.numberNode(qindex));
|
||||
out.set("step", JsonNodeFactory.instance.numberNode(step));
|
||||
out.set("data", qdata);
|
||||
return out;
|
||||
}
|
||||
|
||||
public QuizzForm newQuizzForm(User user, Quizz quizz) {
|
||||
@ -108,8 +157,15 @@ public class QuizzManagerImpl implements QuizzManager {
|
||||
qf.setDone(false);
|
||||
qf.setCurrentQuestion(0);
|
||||
qf.setAnswerStep(0);
|
||||
//XXX Persist QF ?
|
||||
qfRepository.save(qf);
|
||||
return qf;
|
||||
}
|
||||
|
||||
public static final JsonNode errorNode(String err){
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(false));
|
||||
out.set("message", JsonNodeFactory.instance.textNode(err));
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
package com.bernard.misael.service.exception;
|
||||
|
||||
public class MalformedAnswerException extends QuestionTypeException{
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.bernard.misael.service.exception;
|
||||
|
||||
public class MalformedClientAnswerException extends QuestionTypeException {
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.bernard.misael.service.exception;
|
||||
|
||||
public abstract class QuestionTypeException extends Exception{
|
||||
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.bernard.misael.service.exception;
|
||||
|
||||
public class ShouldNotAnswerNowException extends Exception{
|
||||
|
||||
}
|
||||
@ -13,6 +13,7 @@ import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.repository.UserRepository;
|
||||
import com.bernard.misael.service.QuizzManager;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
@ -71,17 +72,19 @@ public class QuestionsController {
|
||||
User u = null;
|
||||
if (p!=null)
|
||||
u = ur.findByName(p.getName());
|
||||
if(u==null)
|
||||
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
|
||||
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) {
|
||||
public ResponseEntity<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;
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -6,8 +6,50 @@
|
||||
|
||||
<body>
|
||||
<div th:replace="~{header}"/>
|
||||
<script th:src="@{/webjars/jquery/1.9.1/jquery.min.js}"></script>
|
||||
<main>
|
||||
Youhou ! (Y devrait y avoir le form ici ^^) <span th:text="${formid}"/>
|
||||
Youhou ! (Y devrait y avoir le form ici ^^) <span th:text="${formid}"/> <br/>
|
||||
|
||||
<button id="question-button">Poser la question</button>
|
||||
<button id="answer1-button">Première réponse</button>
|
||||
<script>
|
||||
e = new RegExp("/([0-9]+)$")
|
||||
e.test(window.location.href)
|
||||
qid = Number(RegExp.lastMatch.substring(1))
|
||||
qindex = 0
|
||||
qstep = 0
|
||||
function next() {
|
||||
$.ajax({
|
||||
url: "/questions/question/"+qid,
|
||||
type: "GET",
|
||||
dataType: "json",
|
||||
success: function(res) {
|
||||
console.log(res)
|
||||
qindex=res["index"]
|
||||
qstep=res["step"]
|
||||
}
|
||||
})
|
||||
}
|
||||
function answer() {
|
||||
asw={
|
||||
index: qindex,
|
||||
step: qstep,
|
||||
data: Math.floor(Math.random() * 3)*2
|
||||
}
|
||||
$.ajax({
|
||||
contentType: 'application/json',
|
||||
type: "POST",
|
||||
url: "/questions/answer/"+qid,
|
||||
data: JSON.stringify(asw),
|
||||
dataType: "json",
|
||||
success: function(res) {
|
||||
console.log(res)
|
||||
}
|
||||
})
|
||||
}
|
||||
$("#question-button").on('click',next)
|
||||
$("#answer1-button").on('click',answer)
|
||||
</script>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
x
Reference in New Issue
Block a user