Ajout d'une page d'édition des quizz
This commit is contained in:
parent
8ae7457016
commit
c62f070e39
@ -41,6 +41,7 @@ dependencies {
|
||||
implementation Spring.session.jdbc
|
||||
implementation 'jakarta.validation:jakarta.validation-api:_'
|
||||
runtimeOnly 'org.webjars:jquery:_'
|
||||
runtimeOnly 'org.webjars:lodash:_'
|
||||
developmentOnly Spring.boot.devTools
|
||||
runtimeOnly 'org.postgresql:postgresql:_'
|
||||
testImplementation Spring.boot.test
|
||||
|
||||
@ -7,15 +7,12 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Entity
|
||||
@Table(name="questions")
|
||||
public class Question {
|
||||
@ -26,15 +23,19 @@ public class Question {
|
||||
private long id;
|
||||
|
||||
@Column(nullable=false)
|
||||
@Setter
|
||||
QTypes type;
|
||||
|
||||
@Column(nullable=false)
|
||||
@Setter
|
||||
private int index;
|
||||
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "quizz",nullable = false)
|
||||
@Setter
|
||||
private Quizz quizz;
|
||||
|
||||
@Setter
|
||||
private String value;
|
||||
|
||||
transient QuestionType qtype = null;
|
||||
@ -44,7 +45,7 @@ public class Question {
|
||||
JsonNode jsNode;
|
||||
try {
|
||||
jsNode = om.readTree(value);
|
||||
qtype = type.getConstructor().apply(jsNode);
|
||||
qtype = type.construct(jsNode);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
@ -4,12 +4,20 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
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.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
@ -82,4 +90,28 @@ public class DccQuestion implements QuestionType{
|
||||
}
|
||||
}
|
||||
|
||||
public static Logger LOG = LoggerFactory.getLogger(DccQuestion.class);
|
||||
|
||||
public static boolean validate(JsonNode data) {
|
||||
LOG.info(data.toPrettyString());
|
||||
LOG.info(Stream.of(data.get("wrong")).map(p -> p.isTextual()+"->"+p).collect(Collectors.joining(";")));
|
||||
return data.isObject() &&
|
||||
data.has("text") && data.get("text").isTextual() &&
|
||||
data.has("good") && data.get("good").isTextual() &&
|
||||
data.has("wrong") && data.get("wrong").isArray() &&
|
||||
data.get("wrong").size() >= 3 &&
|
||||
StreamSupport.stream(Spliterators.spliterator(data.get("wrong").elements(),data.get("wrong").size(),Spliterator.SIZED),false).allMatch(p ->
|
||||
p.isTextual()
|
||||
);
|
||||
}
|
||||
|
||||
public static final JsonNode getDefaultDccQuestion() {
|
||||
ObjectNode q = JsonNodeFactory.instance.objectNode();
|
||||
q.set("text",JsonNodeFactory.instance.textNode(""));
|
||||
q.set("good",JsonNodeFactory.instance.textNode(""));
|
||||
ArrayNode wrong = JsonNodeFactory.instance.arrayNode(3);
|
||||
for(int i=0;i<3;i++)wrong.add(JsonNodeFactory.instance.textNode(""));
|
||||
q.set("wrong",wrong);
|
||||
return q;
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,15 +7,18 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
@AllArgsConstructor
|
||||
public enum QTypes {
|
||||
|
||||
DCC(1,DccQuestion.class,DccQuestion::new);
|
||||
DCC(1,DccQuestion.class,DccQuestion::new,DccQuestion::validate,DccQuestion.getDefaultDccQuestion());
|
||||
|
||||
private int id;
|
||||
@SuppressWarnings("unused")
|
||||
private Class<? extends QuestionType> type;
|
||||
private Function<JsonNode,QuestionType> constructor;
|
||||
private Function<JsonNode,Boolean> validator;
|
||||
@Getter
|
||||
private JsonNode defaultQuestion;
|
||||
|
||||
public static QTypes findById(final int id) {
|
||||
for(QTypes value : QTypes.values())
|
||||
@ -24,4 +27,12 @@ public enum QTypes {
|
||||
return null;
|
||||
}
|
||||
|
||||
public QuestionType construct(JsonNode data){
|
||||
return this.constructor.apply(data);
|
||||
}
|
||||
|
||||
public boolean validate(JsonNode data){
|
||||
return this.validator.apply(data);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
package com.bernard.misael.repository;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
|
||||
import com.bernard.misael.model.Question;
|
||||
@ -9,5 +11,6 @@ import com.bernard.misael.model.Quizz;
|
||||
public interface QuestionRepository extends JpaRepository<Question,Long> {
|
||||
|
||||
public Question findByQuizzAndIndex(Quizz quizz, int index);
|
||||
public Set<Question> findByQuizz(Quizz quizz);
|
||||
|
||||
}
|
||||
@ -4,6 +4,7 @@ import java.util.List;
|
||||
|
||||
import com.bernard.misael.model.Quizz;
|
||||
import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.questions.QTypes;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
|
||||
public interface QuizzManager {
|
||||
@ -13,5 +14,15 @@ public interface QuizzManager {
|
||||
|
||||
public boolean canAccessQuizz(User user, long quizzId);
|
||||
public List<Quizz> accessibleQuizz(User user);
|
||||
|
||||
public boolean canEditQuizz(User user, long quizzId);
|
||||
|
||||
public JsonNode getQuizzInfo(User user, long quizzId);
|
||||
public JsonNode setQuizzName(User user, long quizzId, String newName);
|
||||
public JsonNode addQuestion(User user, long quizzId);
|
||||
public JsonNode removeQuestion(User user, long quizzId, long questionId);
|
||||
public JsonNode reorderQuestions(User user, long quizzId, List<Long> newOrder);
|
||||
public JsonNode editQuestion(User user, long quizzId, long questionId, JsonNode value);
|
||||
public JsonNode setQuestionType(User user, long quizzId, long questionId, QTypes type);
|
||||
|
||||
}
|
||||
@ -1,11 +1,15 @@
|
||||
package com.bernard.misael.service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@ -14,6 +18,7 @@ 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.QTypes;
|
||||
import com.bernard.misael.questions.QuestionType.AnswerResult;
|
||||
import com.bernard.misael.repository.AnswerRepository;
|
||||
import com.bernard.misael.repository.QuestionRepository;
|
||||
@ -22,7 +27,11 @@ import com.bernard.misael.repository.QuizzRepository;
|
||||
import com.bernard.misael.service.exception.MalformedAnswerException;
|
||||
import com.bernard.misael.service.exception.MalformedClientAnswerException;
|
||||
import com.bernard.misael.service.exception.QuestionTypeException;
|
||||
import com.bernard.misael.web.QuestionsController;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
@ -198,4 +207,227 @@ public class QuizzManagerImpl implements QuizzManager {
|
||||
).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(QuestionsController.class);
|
||||
@Override
|
||||
public boolean canEditQuizz(User user, long quizzId) {
|
||||
try {
|
||||
Quizz quizz = qRepository.getReferenceById(quizzId);
|
||||
logger.info("Quizz owner is "+quizz.getOwner().getName());
|
||||
return quizz.getOwner().equals(user);
|
||||
} catch (EntityNotFoundException e) {
|
||||
logger.info("Could not find quizz of id "+quizzId);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Optional<JsonNode> checkEditQuizz(User user, long quizzId) {
|
||||
if(user == null)
|
||||
return Optional.of(errorNode("You need to be logged in to edit the quizz"));
|
||||
if(!canEditQuizz(user, quizzId))
|
||||
return Optional.of(errorNode("User has no right to edit quizz"));
|
||||
Optional<Quizz> oquizz = qRepository.findById(quizzId);
|
||||
if(!oquizz.isPresent())
|
||||
return Optional.of(errorNode("Could not find quizz with id "+quizzId));
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public JsonNode getQuizzInfo(User user, long quizzId) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
ArrayNode n = JsonNodeFactory.instance.arrayNode(quizz.getQuestionCount());
|
||||
for(int i = 0;i<quizz.getQuestionCount();i++)n.add(JsonNodeFactory.instance.nullNode());
|
||||
for(Question q : quizz.getQuestions()) {
|
||||
ObjectNode nn = JsonNodeFactory.instance.objectNode();
|
||||
nn.set("id",JsonNodeFactory.instance.numberNode(q.getId()));
|
||||
nn.set("type", JsonNodeFactory.instance.textNode(q.getType().name()));
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
try {
|
||||
nn.set("value",om.readTree(q.getValue()));
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Value stored for question "+q.getId()+" is illegal (non-json)",e);
|
||||
}
|
||||
n.set(q.getIndex(),nn);
|
||||
}
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
out.set("questions", n);
|
||||
out.set("name", JsonNodeFactory.instance.textNode(quizz.getName()));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode setQuizzName(User user, long quizzId, String newName) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
if(newName.isBlank() | newName.length()>255)
|
||||
return errorNode("Le nom est invalide");
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
quizz.setName(newName);
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
return out;
|
||||
}
|
||||
|
||||
public static final QTypes DEFAULT_QTYPE = QTypes.DCC;
|
||||
|
||||
@Override
|
||||
public JsonNode addQuestion(User user, long quizzId) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
Question q = new Question();
|
||||
q.setType(DEFAULT_QTYPE);
|
||||
JsonNode n = DEFAULT_QTYPE.getDefaultQuestion();
|
||||
q.setValue(n.toString());
|
||||
q.setQuizz(quizz);
|
||||
q.setIndex(quizz.getQuestionCount());
|
||||
quizz.setQuestionCount(quizz.getQuestionCount()+1);
|
||||
questionRepository.save(q);
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
out.set("type", JsonNodeFactory.instance.textNode(DEFAULT_QTYPE.name()));
|
||||
out.set("value", n);
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode removeQuestion(User user, long quizzId, long questionId) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
final Question q;
|
||||
try {
|
||||
q = questionRepository.getReferenceById(questionId);
|
||||
} catch (EntityNotFoundException e){
|
||||
return errorNode("Could not find question with id "+questionId);
|
||||
}
|
||||
|
||||
questionRepository.findByQuizz(quizz).forEach(qq -> {
|
||||
if(qq.getIndex()>q.getIndex())
|
||||
qq.setIndex(qq.getIndex()-1);
|
||||
});
|
||||
questionRepository.delete(q);
|
||||
quizz.setQuestionCount(quizz.getQuestionCount()-1);
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode reorderQuestions(User user, long quizzId, List<Long> newOrder) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
// We need that the base set of neworder is the list of ids
|
||||
// 1) The length is right
|
||||
if(quizz.getQuestionCount() != newOrder.size())
|
||||
return errorNode("You must put every question in order");
|
||||
// 2) There is no duplicates
|
||||
if(new HashSet<Long>(newOrder).size() != newOrder.size())
|
||||
return errorNode("You shouldn't put duplicates in the new order");
|
||||
// 3) All ids correspond to an actual question of the right quizz
|
||||
List<Question> questions = new ArrayList<>(newOrder.size());
|
||||
for(int i = 0;i<newOrder.size();i++){
|
||||
try {
|
||||
Question q = questionRepository.getReferenceById(newOrder.get(i));
|
||||
if(!q.getQuizz().equals(quizz))
|
||||
return errorNode("The question id "+newOrder.get(i)+" is associated to another quizz");
|
||||
questions.add(q);
|
||||
} catch (EntityNotFoundException e){
|
||||
return errorNode("Could not find question with id "+newOrder.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
// All Checks passed
|
||||
for(int i=0;i<questions.size();i++){
|
||||
questions.get(i).setIndex(i);
|
||||
}
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode editQuestion(User user, long quizzId, long questionId, JsonNode value) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
final Question q;
|
||||
try {
|
||||
q = questionRepository.getReferenceById(questionId);
|
||||
} catch (EntityNotFoundException e){
|
||||
return errorNode("Could not find question with id "+questionId);
|
||||
}
|
||||
|
||||
if(!q.getQuizz().equals(quizz))
|
||||
return errorNode("Question is not associated with the right quizzId");
|
||||
|
||||
if(!q.getType().validate(value))
|
||||
return errorNode("Invalid question value");
|
||||
q.setValue(value.toString());
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonNode setQuestionType(User user, long quizzId, long questionId, QTypes type) {
|
||||
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
|
||||
if(authCheck.isPresent()) return authCheck.get();
|
||||
|
||||
Quizz quizz = qRepository.findById(quizzId).get();
|
||||
|
||||
final Question q;
|
||||
try {
|
||||
q = questionRepository.getReferenceById(questionId);
|
||||
} catch (EntityNotFoundException e){
|
||||
return errorNode("Could not find question with id "+questionId);
|
||||
}
|
||||
|
||||
if(!q.getQuizz().equals(quizz))
|
||||
return errorNode("Question is not associated with the right quizzId");
|
||||
|
||||
// If type is the same, we don't change (and dont reset the value)
|
||||
JsonNode n;
|
||||
if(!type.equals(q.getType())) {
|
||||
// Then we need to change
|
||||
q.setType(type);
|
||||
n = type.getDefaultQuestion();
|
||||
q.setValue(n.toString());
|
||||
} else {
|
||||
ObjectMapper om = new ObjectMapper();
|
||||
try {
|
||||
n = om.readTree(q.getValue());
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new IllegalStateException("Value stored for question "+questionId+" is illegal (non-json)",e);
|
||||
}
|
||||
}
|
||||
|
||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||
out.set("new", n);
|
||||
return out;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
package com.bernard.misael.web;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
@ -11,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
import com.bernard.misael.model.Quizz;
|
||||
import com.bernard.misael.model.User;
|
||||
import com.bernard.misael.questions.QTypes;
|
||||
import com.bernard.misael.repository.QuizzRepository;
|
||||
import com.bernard.misael.repository.UserRepository;
|
||||
import com.bernard.misael.service.QuizzManager;
|
||||
@ -24,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
|
||||
|
||||
|
||||
@Controller
|
||||
@RequestMapping("/questions")
|
||||
public class QuestionsController {
|
||||
@ -89,7 +97,125 @@ public class QuestionsController {
|
||||
JsonNode out = qm.answer(u, quizzId, data);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(QuestionsController.class);
|
||||
@GetMapping("/quizz-edit/{q}")
|
||||
public Object quizzEdit(@PathVariable("q") long quizzId, Principal p, Model m) {
|
||||
if (p==null)
|
||||
return "redirect:/login?restricted";
|
||||
User u = ur.findByName(p.getName());
|
||||
logger.info("An INFO Message");
|
||||
if (u==null || !qm.canEditQuizz(u, quizzId))
|
||||
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
|
||||
m.addAttribute("quizzId", quizzId);
|
||||
|
||||
return "quizz-edit";
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/get")
|
||||
public ResponseEntity<JsonNode> quizzSetName(@PathVariable("q") long quizzId, Principal p) {
|
||||
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.getQuizzInfo(u, quizzId);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/set-name")
|
||||
public ResponseEntity<JsonNode> quizzSetName(@PathVariable("q") long quizzId, @RequestBody String data, Principal p) {
|
||||
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.setQuizzName(u, quizzId, data);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/add-question")
|
||||
public ResponseEntity<JsonNode> quizzAddQuestion(@PathVariable("q") long quizzId, Principal p) {
|
||||
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.addQuestion(u, quizzId);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/remove-question/{qi}")
|
||||
public ResponseEntity<JsonNode> quizzSetName(@PathVariable("q") long quizzId, @PathVariable("qi") long questionId, Principal p) {
|
||||
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.removeQuestion(u, quizzId, questionId);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/reorder-questions")
|
||||
public ResponseEntity<JsonNode> quizzReorderQuestions(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p) {
|
||||
User u = null;
|
||||
if (p!=null)
|
||||
u = ur.findByName(p.getName());
|
||||
if(u==null)
|
||||
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
|
||||
if(!data.isArray())
|
||||
return new ResponseEntity<>(
|
||||
JsonNodeFactory.instance.textNode("Data should be an array"),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
List<Long> idz = new ArrayList<>(data.size());
|
||||
for(int i=0;i<data.size();i++)
|
||||
if(data.get(i).isNumber())
|
||||
idz.add(data.get(i).asLong());
|
||||
else
|
||||
return new ResponseEntity<>(
|
||||
JsonNodeFactory.instance.textNode("Data should be an array of numbers"),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
JsonNode out = qm.reorderQuestions(u, quizzId, idz);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/edit-question/{qi}")
|
||||
public ResponseEntity<JsonNode> quizzEditQuestion(@PathVariable("q") long quizzId,
|
||||
@PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p) {
|
||||
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.editQuestion(u, quizzId, questionId, data);
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
@PostMapping("/quizz-edit/{q}/set-question-type/{qi}")
|
||||
public ResponseEntity<JsonNode> quizzSetQuestionType(@PathVariable("q") long quizzId,
|
||||
@PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p) {
|
||||
User u = null;
|
||||
if (p!=null)
|
||||
u = ur.findByName(p.getName());
|
||||
if(u==null)
|
||||
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
|
||||
if(!data.isTextual())
|
||||
return new ResponseEntity<>(
|
||||
JsonNodeFactory.instance.textNode("Data should be a string"),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
Optional<QTypes> qtype = Arrays.stream(QTypes.values())
|
||||
.filter(q -> q.name().equals(data.textValue()))
|
||||
.findAny();
|
||||
if(qtype.isEmpty())
|
||||
return new ResponseEntity<>(
|
||||
JsonNodeFactory.instance.textNode("Unknown qtype"),
|
||||
HttpStatus.BAD_REQUEST);
|
||||
|
||||
JsonNode out = qm.setQuestionType(u, quizzId, questionId, qtype.get());
|
||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -33,3 +33,7 @@ spring:
|
||||
hibernate:
|
||||
ddl-auto: none
|
||||
generate-ddl: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
41
src/main/resources/static/css/quizz-edit.css
Normal file
41
src/main/resources/static/css/quizz-edit.css
Normal file
@ -0,0 +1,41 @@
|
||||
ol#questions-list {
|
||||
display: table;
|
||||
border-spacing: 2px;
|
||||
width: 90%;
|
||||
}
|
||||
ol#questions-list li {
|
||||
width: 90%;
|
||||
background-color: lemonchiffon;
|
||||
border: 2pt solid;
|
||||
display: table-row;
|
||||
flex-direction: row;
|
||||
align-items: stretch;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
ol#questions-list li div.button-box {
|
||||
display:table-cell;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
ol#questions-list li div.button-box button{
|
||||
width: 7em;
|
||||
}
|
||||
|
||||
ol#questions-list li div.content-box {
|
||||
display:table-cell;
|
||||
background-color: gainsboro;
|
||||
color: black;
|
||||
font-size: 12pt;
|
||||
}
|
||||
|
||||
|
||||
/* DCC */
|
||||
div.dcc-box h5, div.dcc-box h5 input{
|
||||
font-size: 16pt;
|
||||
font-weight: bold;
|
||||
text-decoration: underline;
|
||||
width: 100%;
|
||||
}
|
||||
204
src/main/resources/templates/quizz-edit.html
Normal file
204
src/main/resources/templates/quizz-edit.html
Normal file
@ -0,0 +1,204 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr" dir="ltr">
|
||||
<head>
|
||||
<div th:replace="~{html-head}"/>
|
||||
<link rel="stylesheet" th:href="@{/css/quizz-edit.css}"/>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div th:replace="~{header}"/>
|
||||
<script th:src="@{/webjars/jquery/1.9.1/jquery.min.js}" type="text/javascript"></script>
|
||||
<script th:src="@{/webjars/lodash/4.17.21/lodash.js}" type="text/javascript"></script>
|
||||
<main>
|
||||
<h1>Quizz <span id="question-name"></span><button id="edit-question-name">Edit</button></h1>
|
||||
<ol id="questions-list">
|
||||
</ol>
|
||||
<span id="error-textbox" style="color: red"></span>
|
||||
<script th:inline="javascript">
|
||||
/*<![CDATA[*/
|
||||
var quizzid = /*[[${quizzId}]]*/ -1;
|
||||
/*]]>*/
|
||||
</script>
|
||||
<script>
|
||||
function error(txt) {
|
||||
console.log(txt)
|
||||
$("#error-textbox").text(txt)
|
||||
}
|
||||
QTYPES = {
|
||||
"DCC": {
|
||||
"display" : makeAnswerBlockBCC,
|
||||
"edit": makeEditBlockBCC,
|
||||
"read": readEditBlockBCC
|
||||
}
|
||||
}
|
||||
questions={}
|
||||
function getdata() {
|
||||
$.ajax({
|
||||
url: "/questions/quizz-edit/"+quizzid+"/get",
|
||||
type: "POST",
|
||||
dataType: "json",
|
||||
success: function(res) {
|
||||
console.log(res)
|
||||
if(!res["success"]) {
|
||||
console.log(res["message"])
|
||||
error(res["message"])
|
||||
return
|
||||
}
|
||||
questions=res["questions"]
|
||||
qname=res["name"]
|
||||
$("#question-name").text(qname)
|
||||
for(var i=0;i<questions.length;i++){
|
||||
qu = questions[i]
|
||||
qid = qu["id"]
|
||||
newhtml = `
|
||||
<li id="question-display-${qid}">
|
||||
<div class="content-box" id="question-display-${qid}-content"/>
|
||||
<div class="content-box" id="question-display-${qid}-edit"/>
|
||||
<div class="button-box">
|
||||
<button id="question-display-${qid}-button-edit">Edit</button>
|
||||
<button id="question-display-${qid}-type-edit">Type</button>
|
||||
<button id="question-display-${qid}-validate-edit">Valider</button>
|
||||
<button id="question-display-${qid}-cancel-edit">Annuler</button>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
newdom = $('<li/>').html(newhtml).contents()
|
||||
$("#questions-list").append(newdom)
|
||||
const thei = i
|
||||
const theqid = qid
|
||||
$(`#question-display-${qid}-button-edit`).on('click',e => edit(thei,theqid))
|
||||
$(`#question-display-${qid}-type-edit`).on('click',e => setType(thei,theqid))
|
||||
$(`#question-display-${qid}-validate-edit`).on('click',e => commitEdit(thei,theqid))
|
||||
$(`#question-display-${qid}-cancel-edit`).on('click',e => cancelEdit(thei,theqid))
|
||||
|
||||
newcontent = QTYPES[qu["type"]]["display"](qid,qu["value"])
|
||||
$(`#question-display-${qid}-content`).replaceWith(newcontent)
|
||||
newedit = QTYPES[qu["type"]]["edit"](qid,qu["value"])
|
||||
$(`#question-display-${qid}-edit`).replaceWith(newedit)
|
||||
|
||||
contentMode(qid)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function contentMode(qid) {
|
||||
$(`#question-display-${qid}-content`).show()
|
||||
$(`#question-display-${qid}-button-edit`).show()
|
||||
$(`#question-display-${qid}-type-edit`).show()
|
||||
$(`#question-display-${qid}-edit`).hide()
|
||||
$(`#question-display-${qid}-validate-edit`).hide()
|
||||
$(`#question-display-${qid}-cancel-edit`).hide()
|
||||
}
|
||||
function editMode(qid) {
|
||||
$(`#question-display-${qid}-content`).hide()
|
||||
$(`#question-display-${qid}-button-edit`).hide()
|
||||
$(`#question-display-${qid}-type-edit`).hide()
|
||||
$(`#question-display-${qid}-edit`).show()
|
||||
$(`#question-display-${qid}-validate-edit`).show()
|
||||
$(`#question-display-${qid}-cancel-edit`).show()
|
||||
}
|
||||
function lockEdit(qid) {
|
||||
$(`#question-display-${qid}-edit *`).prop('disabled', true)
|
||||
$(`#question-display-${qid}-validate-edit`).prop('disabled', true)
|
||||
$(`#question-display-${qid}-cancel-edit`).prop('disabled', true)
|
||||
}
|
||||
function unlockEdit(qid) {
|
||||
$(`#question-display-${qid}-edit *`).prop('disabled', false)
|
||||
$(`#question-display-${qid}-validate-edit`).prop('disabled', false)
|
||||
$(`#question-display-${qid}-cancel-edit`).prop('disabled', false)
|
||||
}
|
||||
|
||||
function setType(i,id) {
|
||||
//TODO à faire ^^
|
||||
}
|
||||
|
||||
function edit(id, qid) {
|
||||
editMode(qid)
|
||||
}
|
||||
|
||||
function commitEdit(i,qid) {
|
||||
const newvalue = readEditBlockBCC(qid)
|
||||
lockEdit(qid)
|
||||
$.ajax({
|
||||
url: "/questions/quizz-edit/"+quizzid+"/edit-question/"+qid,
|
||||
type: "POST",
|
||||
data: JSON.stringify(newvalue),
|
||||
dataType: "json",
|
||||
contentType: 'application/json',
|
||||
success: function(res) {
|
||||
console.log(res)
|
||||
if(!res["success"]) {
|
||||
console.log(res["message"])
|
||||
error(res["message"])
|
||||
unlockEdit(qid)
|
||||
return
|
||||
}
|
||||
questions[i]["value"] = newvalue
|
||||
newcontent = QTYPES[questions[i]["type"]]["display"](qid,questions[i]["value"])
|
||||
$(`#question-display-${qid}-content`).replaceWith(newcontent)
|
||||
|
||||
unlockEdit(qid)
|
||||
contentMode(qid)
|
||||
}
|
||||
})
|
||||
}
|
||||
function cancelEdit(i,qid) {
|
||||
newedit = QTYPES[questions[i]["type"]]["edit"](qid,questions[i]["value"])
|
||||
$(`#question-display-${qid}-edit`).replaceWith(newedit)
|
||||
contentMode(qid)
|
||||
}
|
||||
|
||||
function makeEditBlockBCC(id,question) {
|
||||
html = `
|
||||
<div class="content-box dcc-box" id="question-display-${id}-edit">
|
||||
<h5><input id="question-display-${id}-edit-question"
|
||||
type="text" value="${_.escape(question["text"])}"/></h5>
|
||||
<ol>
|
||||
<li class="right">☑ <input id="question-display-${id}-edit-good"
|
||||
type="text" value="${_.escape(question["good"])}"/></li>
|
||||
<li class="wrong">☒ <input id="question-display-${id}-edit-wrong1"
|
||||
type="text" value="${_.escape(question["wrong"][0])}"/></li>
|
||||
<li class="wrong">☒ <input id="question-display-${id}-edit-wrong2"
|
||||
type="text" value="${_.escape(question["wrong"][1])}"/></li>
|
||||
<li class="wrong">☒ <input id="question-display-${id}-edit-wrong3"
|
||||
type="text" value="${_.escape(question["wrong"][2])}"/></li>
|
||||
</ol>
|
||||
</div>
|
||||
`
|
||||
out = $('<div/>').html(html).contents()
|
||||
return out
|
||||
}
|
||||
function readEditBlockBCC(id) {
|
||||
questionText = $(`#question-display-${id}-edit-question`).val()
|
||||
goodText = $(`#question-display-${id}-edit-good`).val()
|
||||
wrongText1 = $(`#question-display-${id}-edit-wrong1`).val()
|
||||
wrongText2 = $(`#question-display-${id}-edit-wrong2`).val()
|
||||
wrongText3 = $(`#question-display-${id}-edit-wrong3`).val()
|
||||
return {
|
||||
"text": questionText,
|
||||
"good": goodText,
|
||||
"wrong": [wrongText1,wrongText2,wrongText3]
|
||||
}
|
||||
}
|
||||
function makeAnswerBlockBCC(id,question) {
|
||||
html = `
|
||||
<div class="content-box dcc-box" id="question-display-${id}-content">
|
||||
<h5>${_.escape(question["text"])}</h5>
|
||||
<ol>
|
||||
<li class="right">☑ ${_.escape(question["good"])}</li>
|
||||
<li class="wrong">☒ ${_.escape(question["wrong"][0])}</li>
|
||||
<li class="wrong">☒ ${_.escape(question["wrong"][1])}</li>
|
||||
<li class="wrong">☒ ${_.escape(question["wrong"][2])}</li>
|
||||
</ol>
|
||||
</div>
|
||||
`
|
||||
out = $('<div/>').html(html).contents()
|
||||
return out
|
||||
}
|
||||
|
||||
getdata()
|
||||
</script>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@ -8,7 +8,7 @@
|
||||
<div th:replace="~{header}"/>
|
||||
<main>
|
||||
<ul>
|
||||
<li>Premier item ?</li>
|
||||
<li th:if="${#lists.isEmpty(quizz)}">Aucun quizz de disponible malheureusement :(</li>
|
||||
<li th:each="q : ${quizz}"><a th:href="@{/questions/form/{id}(id=${q.id})}">Quizz <span th:text="${q.name}"/></a></li>
|
||||
</ul>
|
||||
</main>
|
||||
|
||||
@ -3,6 +3,8 @@ package com.bernard.misael;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
import com.bernard.misael.questions.QTypes;
|
||||
|
||||
@SpringBootTest
|
||||
class MisaelApplicationTests {
|
||||
|
||||
@ -10,4 +12,11 @@ class MisaelApplicationTests {
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
@Test
|
||||
void validateDefaultQuestions() {
|
||||
for(QTypes qt : QTypes.values()){
|
||||
assert qt.validate(qt.getDefaultQuestion());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -96,3 +96,5 @@ plugin.org.flywaydb.flyway=11.3.2
|
||||
## # available=11.3.4
|
||||
|
||||
plugin.io.spring.dependency-management=1.1.7
|
||||
|
||||
version.org.webjars..lodash=4.17.21
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user