Code refactors

This commit is contained in:
Mysaa Java
2026-07-15 03:37:49 +02:00
parent 430488ceb8
commit 48fbc434a4
12 changed files with 363 additions and 337 deletions
+5
View File
@@ -33,3 +33,8 @@ spring:
bcom: bcom:
issuer-uri: "https://auth.example.com/realms/bcom" issuer-uri: "https://auth.example.com/realms/bcom"
``` ```
## API
All API requests must be done with POST requests. They will always output a json object, with a boolean
filed `success`. If `success` is false, a field `message` can give more information on the error.
@@ -4,7 +4,6 @@ import java.security.Principal;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
@@ -26,13 +25,12 @@ import lombok.Getter;
@Controller @Controller
public class AuthController { public class AuthController {
@Autowired private final UserService userService;
private UserService userService; private final UserRepository urepo;
@Autowired
private UserRepository urepo;
public AuthController(UserService userService) { public AuthController(UserService userService, UserRepository urepo) {
this.userService = userService; this.userService = userService;
this.urepo = urepo;
} }
@GetMapping("/login") @GetMapping("/login")
@@ -94,21 +92,40 @@ public class AuthController {
return "redirect:/change-password?success"; return "redirect:/change-password?success";
} }
public static String requiresLogin(String redirect) {
//TODO Make it so it really redirects after login
return "redirect:/login?restricted";
}
@Getter @Getter
public static class UserInfo implements Comparable<UserInfo> { public static class UserInfo implements Comparable<UserInfo> {
private long id; private long id;
private String oidcId; private String oidcId;
private String pseudo; private String pseudo;
private String roles; private String roles;
public UserInfo(User u){ public UserInfo(User u){
this.id = u.getId(); this.id = u.getId();
this.oidcId = u.getOidcId(); this.oidcId = u.getOidcId();
this.pseudo = u.getName(); this.pseudo = u.getName();
this.roles = u.getPrivileges().stream().map(Privilege::name).collect(Collectors.joining(";")); this.roles = u.getPrivileges().stream().map(Privilege::name).collect(Collectors.joining(";"));
} }
@Override @Override
public int compareTo(UserInfo other) { public int compareTo(UserInfo other) {
return this.pseudo.compareTo(other.pseudo); return this.pseudo.compareTo(other.pseudo);
} }
@Override
public boolean equals(Object obj) {
if(obj instanceof UserInfo other)
return this.pseudo.equals(other.pseudo);
return false;
}
@Override
public int hashCode() {
return this.pseudo.hashCode();
}
} }
} }
@@ -41,10 +41,11 @@ public class UserServiceImpl implements UserService {
String id = oidcUser.getName(); String id = oidcUser.getName();
Set<Privilege> privileges; Set<Privilege> privileges;
try { try {
if(oidcUser.getAttribute("resource_access") != null) Map<String,Map<String,List<String>>> resourceAccess = oidcUser
.<Map<String,Map<String,List<String>>>>getAttribute("resource_access");
if(resourceAccess == null)
throw new RuntimeException("Oidc user has no 'resource_access'"); throw new RuntimeException("Oidc user has no 'resource_access'");
privileges = oidcUser privileges = resourceAccess
.<Map<String,Map<String,List<String>>>>getAttribute("resource_access")
.getOrDefault("misael",Map.of()) .getOrDefault("misael",Map.of())
.getOrDefault("roles",List.of()) .getOrDefault("roles",List.of())
.stream() .stream()
@@ -103,8 +104,8 @@ public class UserServiceImpl implements UserService {
public List<UserDto> findAllUsers() { public List<UserDto> findAllUsers() {
List<User> users = userRepository.findAll(); List<User> users = userRepository.findAll();
return users.stream() return users.stream()
.map((user) -> mapToUserDto(user)) .map(this::mapToUserDto)
.collect(Collectors.toList()); .toList();
} }
private UserDto mapToUserDto(User user){ private UserDto mapToUserDto(User user){
@@ -123,12 +124,10 @@ public class UserServiceImpl implements UserService {
@Override @Override
public User ofPrincipal(Principal p) { public User ofPrincipal(Principal p) {
User u = null; User u = null;
if(p instanceof OAuth2AuthenticationToken) { if(p instanceof OAuth2AuthenticationToken o) {
OAuth2AuthenticationToken o = (OAuth2AuthenticationToken) p;
u = userRepository.findByOidcId(o.getName()).orElseThrow(() -> new RuntimeException("Oauth2 user was not in the database")); u = userRepository.findByOidcId(o.getName()).orElseThrow(() -> new RuntimeException("Oauth2 user was not in the database"));
} }
if(p instanceof UsernamePasswordAuthenticationToken) { if(p instanceof UsernamePasswordAuthenticationToken o) {
UsernamePasswordAuthenticationToken o = (UsernamePasswordAuthenticationToken) p;
u = userRepository.findByName(o.getName()); u = userRepository.findByName(o.getName());
} }
return u; return u;
@@ -0,0 +1,23 @@
package com.bernard.misael.quizz;
import org.springframework.http.HttpStatus;
import org.springframework.lang.NonNull;
import lombok.AllArgsConstructor;
import lombok.Getter;
@AllArgsConstructor
@Getter
public class APIException extends RuntimeException {
private final String message;
@NonNull
private final HttpStatus status;
public APIException(String message) {
this.message = message;
this.status = HttpStatus.BAD_REQUEST;
}
}
@@ -6,9 +6,7 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.slf4j.Logger; import org.springframework.http.HttpMethod;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.annotation.Secured;
@@ -19,6 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import com.bernard.misael.auth.service.UserService; import com.bernard.misael.auth.service.UserService;
import com.bernard.misael.quizz.model.Quizz; import com.bernard.misael.quizz.model.Quizz;
import com.bernard.misael.quizz.model.QuizzForm; import com.bernard.misael.quizz.model.QuizzForm;
import com.bernard.misael.auth.AuthController;
import com.bernard.misael.auth.model.User; import com.bernard.misael.auth.model.User;
import com.bernard.misael.quizz.questions.QTypes; import com.bernard.misael.quizz.questions.QTypes;
import com.bernard.misael.quizz.repository.QuizzFormRepository; import com.bernard.misael.quizz.repository.QuizzFormRepository;
@@ -26,7 +25,11 @@ import com.bernard.misael.quizz.repository.QuizzRepository;
import com.bernard.misael.quizz.service.QuizzManager; import com.bernard.misael.quizz.service.QuizzManager;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -36,17 +39,29 @@ import org.springframework.web.bind.annotation.RequestBody;
@RequestMapping("/questions") @RequestMapping("/questions")
public class QuestionsController { public class QuestionsController {
@Autowired private final UserService us;
UserService us; private final QuizzManager qm;
private final QuizzRepository qrepo;
private final QuizzFormRepository qfrepo;
@Autowired public QuestionsController(UserService us, QuizzManager qm, QuizzRepository qrepo, QuizzFormRepository qfrepo) {
QuizzManager qm; this.us = us;
this.qm = qm;
this.qrepo = qrepo;
this.qfrepo = qfrepo;
}
@Autowired @ExceptionHandler(APIException.class)
QuizzRepository qrepo; public Object apiException(HttpServletRequest request, APIException e) {
if(request.getMethod().equals(HttpMethod.GET.toString()))
@Autowired return new ResponseEntity<>(e.getMessage(), e.getStatus());
QuizzFormRepository qfrepo; else {
ObjectNode out = JsonNodeFactory.instance.objectNode();
out.set("success", JsonNodeFactory.instance.booleanNode(false));
out.set("message", JsonNodeFactory.instance.textNode(e.getMessage()));
return new ResponseEntity<>(out, e.getStatus());
}
}
/* /*
* List all quizz * List all quizz
@@ -80,16 +95,10 @@ public class QuestionsController {
* Show one (completed) form of one user * Show one (completed) form of one user
*/ */
@GetMapping("/showform/{id}") @GetMapping("/showform/{id}")
public Object showForm(@PathVariable("id") long id, Model m, Principal p) { public Object showForm(@PathVariable("id") long id, Model m, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) QuizzForm qf = qm.getQuizzForm(u, id);
return "redirect:/login?restricted"; m.addAttribute("formId", qf.getId());
Optional<QuizzForm> oqf = qm.canViewQuizzForm(u, id);
if (oqf.isEmpty())
//TODO Faire un mesasge d'erreur dépendant des circonstances (unatuhorized, not found, not complete ...)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
m.addAttribute("formId", id);
return "showform.html"; return "showform.html";
} }
@@ -97,16 +106,10 @@ public class QuestionsController {
* Watch advancements of all forms for a specific quizz id * Watch advancements of all forms for a specific quizz id
*/ */
@GetMapping("/watch/{id}") @GetMapping("/watch/{id}")
public Object showFormsAdvancements(@PathVariable("id") long id, Model m, Principal p) { public Object showFormsAdvancements(@PathVariable("id") long id, Model m, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) Quizz q = qm.getQuizz4Watch(u, id);
return "redirect:/login?restricted"; m.addAttribute("quizzId", q.getId());
Optional<Quizz> oq = qm.canViewQuizzFormsOfQuizz(u, id);
if (oq.isEmpty())
//TODO Faire un message d'erreur dépendant des circonstances (unatuhorized, not found, not complete ...)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
m.addAttribute("quizzId", id);
return "watchquizz.html"; return "watchquizz.html";
} }
@@ -114,10 +117,10 @@ public class QuestionsController {
* API get the form * API get the form
*/ */
@PostMapping("/getformdata/{id}") @PostMapping("/getformdata/{id}")
public Object showFormApi(@PathVariable("id") long id, Principal p) { public Object showFormApi(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.getQuizzFormData(u, id); JsonNode out = qm.getQuizzFormData(u, id);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@@ -125,11 +128,11 @@ public class QuestionsController {
/* /*
* API get the forms for a specific quizz * API get the forms for a specific quizz
*/ */
@GetMapping("/getallformsdata/{id}") @PostMapping("/getallformsdata/{id}")
public Object getAllFormsData(@PathVariable("id") long id, Principal p) { public Object getAllFormsData(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.getAllFormsData(u, id); JsonNode out = qm.getAllFormsData(u, id);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@@ -138,10 +141,10 @@ public class QuestionsController {
* API get the form advancement for every form of a quizz * API get the form advancement for every form of a quizz
*/ */
@PostMapping("/watchdata/{id}") @PostMapping("/watchdata/{id}")
public Object watchData(@PathVariable("id") long id, Principal p) { public Object watchData(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.getQuizzFormAdvancments(u, id); JsonNode out = qm.getQuizzFormAdvancments(u, id);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@@ -149,11 +152,11 @@ public class QuestionsController {
/* /*
* API get the form * API get the form
*/ */
@GetMapping("/duplicate-quizz/{id}") @PostMapping("/duplicate-quizz/{id}")
public Object duplicateQuizz(@PathVariable("id") long id, Principal p) { public Object duplicateQuizz(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
Quizz q = qm.duplicateQuizz(u, id); Quizz q = qm.duplicateQuizz(u, id);
if(q == null) if(q == null)
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
@@ -163,11 +166,11 @@ public class QuestionsController {
/* /*
* API get the form * API get the form
*/ */
@GetMapping("/mark-complete/{id}") @PostMapping("/mark-complete/{id}")
public Object markComplete(@PathVariable("id") long id, Principal p) { public Object markComplete(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
if(!qm.markComplete(u, id)) if(!qm.markComplete(u, id))
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
@@ -176,10 +179,10 @@ public class QuestionsController {
* API set the last public question * API set the last public question
*/ */
@PostMapping("/set-public-question-count/{id}") @PostMapping("/set-public-question-count/{id}")
public Object setPublicQuestionCount(@PathVariable("id") long id, @RequestBody JsonNode data, Principal p) { public Object setPublicQuestionCount(@PathVariable("id") long id, @RequestBody JsonNode data, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
JsonNode pqc = data.get("publicQuestionCount"); JsonNode pqc = data.get("publicQuestionCount");
Integer pqcI = pqc.isNull() ? null : pqc.asInt(); Integer pqcI = pqc.isNull() ? null : pqc.asInt();
if(!qm.setPublicQuestionCount(u, id, pqcI)) if(!qm.setPublicQuestionCount(u, id, pqcI))
@@ -188,10 +191,10 @@ public class QuestionsController {
} }
@GetMapping("/form/{q}") @GetMapping("/form/{q}")
public String formpage(@PathVariable("q") long quizzId, Principal p, Model m) { public String formpage(@PathVariable("q") long quizzId, Principal p, Model m, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if (u==null) if (u==null)
return "redirect:/login?restricted"; return AuthController.requiresLogin(request.getRequestURI());
m.addAttribute("formid", quizzId); m.addAttribute("formid", quizzId);
Quizz q = qrepo.getReferenceById(quizzId); Quizz q = qrepo.getReferenceById(quizzId);
m.addAttribute("quizzLength",q.getQuestionCount()); m.addAttribute("quizzLength",q.getQuestionCount());
@@ -199,85 +202,84 @@ public class QuestionsController {
return "form"; return "form";
} }
@GetMapping("/question/{q}") @PostMapping("/question/{q}")
public ResponseEntity<JsonNode> question(@PathVariable("q") long quizzId, Principal p) { public Object question(@PathVariable("q") long quizzId, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.next(u, quizzId); JsonNode out = qm.next(u, quizzId);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@PostMapping("/answer/{q}") @PostMapping("/answer/{q}")
public ResponseEntity<JsonNode> answer(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p) { public Object answer(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null)
return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.answer(u, quizzId, data); JsonNode out = qm.answer(u, quizzId, data);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@GetMapping("/new-quizz") @GetMapping("/new-quizz")
@Secured("CREATE_QUIZZ") @Secured("CREATE_QUIZZ")
public Object newQuizz(Principal p, Model m) { public Object newQuizz(Principal p, Model m, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if (u==null) if (u==null)
return new ResponseEntity<>(HttpStatus.FORBIDDEN); return AuthController.requiresLogin(request.getRequestURI());
Quizz q = qm.newQuizz(u); Quizz q = qm.newQuizz(u);
return "redirect:/questions/quizz-edit/"+Long.toString(q.getId()); return "redirect:/questions/quizz-edit/"+Long.toString(q.getId());
} }
Logger logger = LoggerFactory.getLogger(QuestionsController.class);
@GetMapping("/quizz-edit/{q}") @GetMapping("/quizz-edit/{q}")
public Object quizzEdit(@PathVariable("q") long quizzId, Principal p, Model m) { public Object quizzEdit(@PathVariable("q") long quizzId, Principal p, Model m, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if (u==null || !qm.canEditQuizz(u, quizzId)) Quizz q = qm.getQuizz4Edit(u, quizzId);
return new ResponseEntity<>(HttpStatus.FORBIDDEN); m.addAttribute("quizzId", q.getId());
m.addAttribute("quizzId", quizzId);
return "quizz-edit"; return "quizz-edit";
} }
@PostMapping("/quizz-edit/{q}/get") @PostMapping("/quizz-edit/{q}/get")
public ResponseEntity<JsonNode> quizzSetName(@PathVariable("q") long quizzId, Principal p) { public Object quizzSetName(@PathVariable("q") long quizzId, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.getQuizzInfo(u, quizzId); JsonNode out = qm.getQuizzInfo(u, quizzId);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@PostMapping("/quizz-edit/{q}/set-name") @PostMapping("/quizz-edit/{q}/set-name")
public ResponseEntity<JsonNode> quizzSetName(@PathVariable("q") long quizzId, @RequestBody String data, Principal p) { public Object quizzSetName(@PathVariable("q") long quizzId, @RequestBody String data, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.setQuizzName(u, quizzId, data); JsonNode out = qm.setQuizzName(u, quizzId, data);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@PostMapping("/quizz-edit/{q}/add-question") @PostMapping("/quizz-edit/{q}/add-question")
public ResponseEntity<JsonNode> quizzAddQuestion(@PathVariable("q") long quizzId, Principal p) { public Object quizzAddQuestion(@PathVariable("q") long quizzId, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.addQuestion(u, quizzId); JsonNode out = qm.addQuestion(u, quizzId);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@PostMapping("/quizz-edit/{q}/remove-question/{qi}") @PostMapping("/quizz-edit/{q}/remove-question/{qi}")
public ResponseEntity<JsonNode> quizzSetName(@PathVariable("q") long quizzId, @PathVariable("qi") long questionId, Principal p) { public Object quizzSetName(@PathVariable("q") long quizzId, @PathVariable("qi") long questionId, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.removeQuestion(u, quizzId, questionId); JsonNode out = qm.removeQuestion(u, quizzId, questionId);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@PostMapping("/quizz-edit/{q}/reorder-questions") @PostMapping("/quizz-edit/{q}/reorder-questions")
public ResponseEntity<JsonNode> quizzReorderQuestions(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p) { public Object quizzReorderQuestions(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p, HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
if(!data.isArray()) if(!data.isArray())
return new ResponseEntity<>( return new ResponseEntity<>(
JsonNodeFactory.instance.textNode("Data should be an array"), JsonNodeFactory.instance.textNode("Data should be an array"),
@@ -295,22 +297,24 @@ public class QuestionsController {
} }
@PostMapping("/quizz-edit/{q}/edit-question/{qi}") @PostMapping("/quizz-edit/{q}/edit-question/{qi}")
public ResponseEntity<JsonNode> quizzEditQuestion(@PathVariable("q") long quizzId, public Object quizzEditQuestion(@PathVariable("q") long quizzId,
@PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p) { @PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p,
HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
JsonNode out = qm.editQuestion(u, quizzId, questionId, data); JsonNode out = qm.editQuestion(u, quizzId, questionId, data);
return new ResponseEntity<>(out, HttpStatus.OK); return new ResponseEntity<>(out, HttpStatus.OK);
} }
@PostMapping("/quizz-edit/{q}/set-question-type/{qi}") @PostMapping("/quizz-edit/{q}/set-question-type/{qi}")
public ResponseEntity<JsonNode> quizzSetQuestionType(@PathVariable("q") long quizzId, public Object quizzSetQuestionType(@PathVariable("q") long quizzId,
@PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p) { @PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p,
HttpServletRequest request) {
User u = us.ofPrincipal(p); User u = us.ofPrincipal(p);
if(u==null) if(u==null)
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED); return AuthController.requiresLogin(request.getRequestURI());
if(!data.isTextual()) if(!data.isTextual())
return new ResponseEntity<>( return new ResponseEntity<>(
JsonNodeFactory.instance.textNode("Data should be a string"), JsonNodeFactory.instance.textNode("Data should be a string"),
@@ -1,9 +1,9 @@
package com.bernard.misael.quizz.service; package com.bernard.misael.quizz.service;
import java.util.List; import java.util.List;
import java.util.Optional;
import com.bernard.misael.auth.model.User; import com.bernard.misael.auth.model.User;
import com.bernard.misael.quizz.APIException;
import com.bernard.misael.quizz.model.Quizz; import com.bernard.misael.quizz.model.Quizz;
import com.bernard.misael.quizz.model.QuizzForm; import com.bernard.misael.quizz.model.QuizzForm;
import com.bernard.misael.quizz.questions.QTypes; import com.bernard.misael.quizz.questions.QTypes;
@@ -16,14 +16,14 @@ public interface QuizzManager {
public Quizz newQuizz(User user); public Quizz newQuizz(User user);
public boolean canAccessQuizz(User user, long quizzId);
public List<Quizz> editableQuizz(User user); public List<Quizz> editableQuizz(User user);
public List<Quizz> completedQuizz(User user); public List<Quizz> completedQuizz(User user);
public List<Quizz> answerableQuizz(User user); public List<Quizz> answerableQuizz(User user);
public boolean canEditQuizz(User user, long quizzId); public Quizz getQuizz4Edit(User user, long quizzId) throws APIException;
public Optional<QuizzForm> canViewQuizzForm(User user, long quizzFormId); public QuizzForm getQuizzForm(User user, long quizzFormId) throws APIException;
public Optional<Quizz> canViewQuizzFormsOfQuizz(User user, long quizzId); public Quizz getQuizz4Watch(User user, long quizzId) throws APIException;
public Quizz getQuizz4Answer(User user, long quizzId) throws APIException;
public JsonNode getQuizzInfo(User user, long quizzId); public JsonNode getQuizzInfo(User user, long quizzId);
public JsonNode setQuizzName(User user, long quizzId, String newName); public JsonNode setQuizzName(User user, long quizzId, String newName);
@@ -6,27 +6,26 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.slf4j.Logger; import org.springframework.http.HttpStatus;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.bernard.misael.auth.repository.UserRepository;
import com.bernard.misael.auth.service.UserService;
import com.bernard.misael.quizz.model.Answer;
import com.bernard.misael.auth.model.Privilege; import com.bernard.misael.auth.model.Privilege;
import com.bernard.misael.auth.model.User;
import com.bernard.misael.auth.service.UserService;
import com.bernard.misael.quizz.APIException;
import com.bernard.misael.quizz.model.Answer;
import com.bernard.misael.quizz.model.Question; import com.bernard.misael.quizz.model.Question;
import com.bernard.misael.quizz.model.Quizz; import com.bernard.misael.quizz.model.Quizz;
import com.bernard.misael.quizz.model.QuizzForm; import com.bernard.misael.quizz.model.QuizzForm;
import com.bernard.misael.auth.model.User;
import com.bernard.misael.quizz.questions.QTypes; import com.bernard.misael.quizz.questions.QTypes;
import com.bernard.misael.quizz.questions.QuestionType.AnswerResult; import com.bernard.misael.quizz.questions.QuestionType.AnswerResult;
import com.bernard.misael.quizz.QuestionsController; import com.bernard.misael.quizz.repository.AnswerRepository;
import com.bernard.misael.quizz.repository.*; import com.bernard.misael.quizz.repository.QuestionRepository;
import com.bernard.misael.quizz.repository.QuizzFormRepository;
import com.bernard.misael.quizz.repository.QuizzRepository;
import com.bernard.misael.quizz.service.exception.MalformedAnswerException; import com.bernard.misael.quizz.service.exception.MalformedAnswerException;
import com.bernard.misael.quizz.service.exception.MalformedClientAnswerException; import com.bernard.misael.quizz.service.exception.MalformedClientAnswerException;
import com.bernard.misael.quizz.service.exception.QuestionTypeException; import com.bernard.misael.quizz.service.exception.QuestionTypeException;
@@ -35,82 +34,80 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.JsonNodeFactory;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import jakarta.persistence.EntityNotFoundException; import jakarta.validation.constraints.NotNull;
@Service @Service
public class QuizzManagerImpl implements QuizzManager { public class QuizzManagerImpl implements QuizzManager {
@Autowired private final QuizzFormRepository qfRepository;
UserRepository uRepository; private final QuizzRepository qRepository;
private final QuestionRepository questionRepository;
@Autowired private final AnswerRepository answerRepository;
QuizzFormRepository qfRepository; private final UserService uService;
@Autowired
QuizzRepository qRepository;
@Autowired
QuestionRepository questionRepository;
@Autowired
AnswerRepository answerRepository;
@Autowired
UserService uService;
public QuizzManagerImpl(
QuizzFormRepository qfRepository,
QuizzRepository qRepository,
QuestionRepository questionRepository,
AnswerRepository answerRepository,
UserService uService
) {
this.qfRepository = qfRepository;
this.qRepository = qRepository;
this.questionRepository = questionRepository;
this.answerRepository = answerRepository;
this.uService = uService;
}
private static final APIException LOGIN_REQUIRED_EXCEPTION =
new APIException("Must be logged in to do this action", HttpStatus.UNAUTHORIZED);
@Override @Override
public JsonNode answer(User user, long quizzId, JsonNode data) { public JsonNode answer(User user, long quizzId, JsonNode data) {
Quizz quizz = getQuizz4Answer(user, quizzId);
if(!data.has("index") || !data.get("index").isInt()) if(!data.has("index") || !data.get("index").isInt())
return errorNode("Request should contain the question index"); throw new APIException("Request should contain the question index");
if(!data.has("step") || !data.get("step").isInt()) if(!data.has("step") || !data.get("step").isInt())
return errorNode("Request should contain the answer step"); throw new APIException("Request should contain the answer step");
if(!data.has("data")) if(!data.has("data"))
return errorNode("Request should contain the answer data"); throw new APIException("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);
if(!oquizz.get().isComplete())
return errorNode("Quizz is not complete");
Quizz quizz = oquizz.get();
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz); QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
if(qf == null) if(qf == null)
return errorNode("The quizzform does not exist, ask the question first"); throw new APIException("The quizzform does not exist, ask the question first", HttpStatus.NOT_FOUND);
if(qf.isDone()) if(qf.isDone())
return errorNode("You're done with the quizz, you cannot answer anymore"); throw new APIException("You're done with the quizz, you cannot answer anymore", HttpStatus.CONFLICT);
int qindex = qf.getCurrentQuestion(); int qindex = qf.getCurrentQuestion();
if(qindex != data.get("index").intValue()) if(qindex != data.get("index").intValue())
return errorNode("You are not answering the right question (you answer question "+data.get("index").intValue() throw new APIException(
+" where you should answer question "+qindex+")"); "You are not answering the right question (you answer question %d, but you should answer question %d)"
.formatted(data.get("index").intValue(), qindex), HttpStatus.CONFLICT);
if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE)) if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE))
return errorNode("La question suivante est encore bloquée"); throw new APIException("La question suivante est encore bloquée", HttpStatus.CONFLICT);
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex); Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
if(q == null) if(q == null)
return errorNode("Could not find question "+qindex); throw new APIException("Could not find question %d".formatted(qindex), HttpStatus.NOT_FOUND);
int step = qf.getAnswerStep(); int step = qf.getAnswerStep();
if(step != data.get("step").intValue()) 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() throw new APIException(
+" where you should step question "+step+")"); "You are not answering the right step of the question (you answer step %d where you should be answering step %d)"
.formatted(data.get("step").intValue(),step));
Answer answer = answerRepository.findByFormAndQuestion(qf, q); Answer answer = answerRepository.findByFormAndQuestion(qf, q);
if(answer == null) if(answer == null)
return errorNode("The database answer object does not exist, ask the question first"); throw new APIException("The database answer object does not exist, ask the question first", HttpStatus.CONFLICT);
JsonNode answerData = answer.getValue(); JsonNode answerData = answer.getValue();
AnswerResult result; AnswerResult result;
try { try {
result = q.getQT().clientAnswers(step, answerData, data.get("data")); result = q.getQT().clientAnswers(step, answerData, data.get("data"));
} catch (MalformedAnswerException e) { } catch (MalformedAnswerException e) {
return errorNode("The previous answer stored in database is invalid"); throw new APIException("The previous answer stored in database is invalid", HttpStatus.INTERNAL_SERVER_ERROR);
} catch (MalformedClientAnswerException e) { } catch (MalformedClientAnswerException e) {
return errorNode("This answer is not valid here"); throw new APIException("This answer is not valid here");
} catch (QuestionTypeException e) { } catch (QuestionTypeException e) {
return errorNode("Unknown error from the QuestionType"); throw new APIException("Unknown error from the QuestionType", HttpStatus.INTERNAL_SERVER_ERROR);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return errorNode("The QuestionType did not recognize the step of the question"); throw new APIException("The QuestionType did not recognize the step of the question");
} }
if(result.isNextQuestion()) { if(result.isNextQuestion()) {
qf.setCurrentQuestion(qindex+1); qf.setCurrentQuestion(qindex+1);
@@ -131,27 +128,20 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public JsonNode next(User user, long quizzId) { public JsonNode next(User user, long quizzId) {
if(user == null) Quizz quizz = getQuizz4Answer(user, quizzId);
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);
if(!oquizz.get().isComplete())
return errorNode("Quizz is not complete");
Quizz quizz = oquizz.get();
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz); QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
if(qf == null){ if(qf == null){
// We should create the quizzform // We should create the quizzform
qf = newQuizzForm(user, quizz); qf = newQuizzForm(user, quizz);
} }
if(qf.isDone()) if(qf.isDone())
return errorNode("No more questions"); throw new APIException("No more questions", HttpStatus.OK);
int qindex = qf.getCurrentQuestion(); int qindex = qf.getCurrentQuestion();
if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE)) if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE))
return errorNode("La question suivante est encore bloquée"); throw new APIException("La question suivante est encore bloquée", HttpStatus.OK);
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex); Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
if(q == null) if(q == null)
return errorNode("Could not find question "+qindex); throw new APIException("Could not find question %d".formatted(qindex), HttpStatus.NOT_FOUND);
int step = qf.getAnswerStep(); int step = qf.getAnswerStep();
Answer answer = answerRepository.findByFormAndQuestion(qf, q); Answer answer = answerRepository.findByFormAndQuestion(qf, q);
if(answer==null){ if(answer==null){
@@ -166,11 +156,11 @@ public class QuizzManagerImpl implements QuizzManager {
try { try {
qdata = q.getQT().clientQuestionData(step, answerData); qdata = q.getQT().clientQuestionData(step, answerData);
} catch (MalformedAnswerException e) { } catch (MalformedAnswerException e) {
return errorNode("The previous answer stored in database is invalid"); throw new APIException("The previous answer stored in database is invalid", HttpStatus.CONFLICT);
} catch (QuestionTypeException e) { } catch (QuestionTypeException e) {
return errorNode("Unknown error from the QuestionType"); throw new APIException("Unknown error from the QuestionType", HttpStatus.INTERNAL_SERVER_ERROR);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
return errorNode("The QuestionType did not recognize the step of the question"); throw new APIException("The QuestionType did not recognize the step of the question");
} }
ObjectNode out = JsonNodeFactory.instance.objectNode(); ObjectNode out = JsonNodeFactory.instance.objectNode();
@@ -181,7 +171,7 @@ public class QuizzManagerImpl implements QuizzManager {
return out; return out;
} }
private QuizzForm newQuizzForm(User user, Quizz quizz) { private QuizzForm newQuizzForm(@NonNull User user, Quizz quizz) {
QuizzForm qf = new QuizzForm(); QuizzForm qf = new QuizzForm();
qf.setUser(user); qf.setUser(user);
qf.setQuizz(quizz); qf.setQuizz(quizz);
@@ -196,29 +186,12 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public Quizz newQuizz(User user) { public Quizz newQuizz(User user) {
Quizz q = new Quizz(); Quizz q = new Quizz();
q.setName("Super questions de "+user.getName()+" ("+Integer.toHexString(rand.nextInt(0xFFFFFFF))+")"); q.setName("Super questions de %s (%X)".formatted(user.getName(),rand.nextInt(0xFFFFFFF)));
q.setOwner(user); q.setOwner(user);
q = qRepository.save(q); q = qRepository.save(q);
return q; return q;
} }
private 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;
}
@Override
public boolean canAccessQuizz(User user, long quizzId) {
try{
Quizz quizz = qRepository.getReferenceById(quizzId);
return quizz.getPublicQuestionCount()!=null || quizz.getOwner().equals(user);
} catch (EntityNotFoundException e) {
return false;
}
}
@Override @Override
public List<Quizz> answerableQuizz(User user) { public List<Quizz> answerableQuizz(User user) {
Set<Quizz> ownQuizz = qRepository.findByOwnerAndIsCompleteTrue(user); Set<Quizz> ownQuizz = qRepository.findByOwnerAndIsCompleteTrue(user);
@@ -249,38 +222,130 @@ public class QuizzManagerImpl implements QuizzManager {
.toList(); .toList();
} }
Logger logger = LoggerFactory.getLogger(QuizzManagerImpl.class); /**
@Override * Checks that the given user can edit the quizz whose id is provided.
public boolean canEditQuizz(User user, long quizzId) { * If not, throws an APIException
try { * @param user The user that makes the request
Quizz quizz = qRepository.getReferenceById(quizzId); * @param quizzId The ID of the quizz we want to get
return quizz.getOwner().equals(user); * @return The Quizz database object
} catch (EntityNotFoundException e) { * @throws APIException If something isn't right
logger.info("Could not find quizz of id {}",quizzId); */
return false; @NotNull
} public Quizz getQuizz4Edit(User user, long quizzId) throws APIException {
}
private Optional<JsonNode> checkEditQuizz(User user, long quizzId) {
if (user == null) if (user == null)
return Optional.of(errorNode("You need to be logged in to edit the quizz")); throw LOGIN_REQUIRED_EXCEPTION;
if(!canEditQuizz(user, quizzId))
return Optional.of(errorNode("User has no right to edit quizz"));
Optional<Quizz> oquizz = qRepository.findById(quizzId); Optional<Quizz> oquizz = qRepository.findById(quizzId);
if (!oquizz.isPresent()) if (!oquizz.isPresent())
return Optional.of(errorNode("Could not find quizz with id "+quizzId)); throw new APIException("Could not find quizz with id %d".formatted(quizzId), HttpStatus.NOT_FOUND);
if(oquizz.get().isComplete()) Quizz q = oquizz.get();
return Optional.of(errorNode("Quizz is complete, cannot edit, answers might have already been cast")); if (!q.getOwner().equals(user))
return Optional.empty(); throw new APIException("User has no right to edit quizz (must be author)", HttpStatus.FORBIDDEN);
if (q.isComplete())
throw new APIException("Quizz is complete, cannot edit, answers might have already been cast", HttpStatus.CONFLICT);
return q;
} }
/**
* Checks that the given user can answer the quizz whose id is provided.
* If not, throws an APIException
* @param user The user that makes the request
* @param quizzId The ID of the quizz we want to get
* @return The Quizz database object
* @throws APIException If something isn't right
*/
@NotNull
public Quizz getQuizz4Answer(User user, long quizzId) throws APIException {
if (user == null)
throw LOGIN_REQUIRED_EXCEPTION;
Optional<Quizz> oquizz = qRepository.findById(quizzId);
if (!oquizz.isPresent())
throw new APIException("Could not find quizz with id %d".formatted(quizzId), HttpStatus.NOT_FOUND);
Quizz q = oquizz.get();
if(!q.isComplete())
throw new APIException("Cannot answer a quizz that is not marked as complete", HttpStatus.CONFLICT);
if (!(
q.getOwner().equals(user) ||
q.getPublicQuestionCount() != null
))
throw new APIException("User has no right to answer quizz (must be author, or quizz must be public)", HttpStatus.FORBIDDEN);
return q;
}
/**
* Gets the specified quizz if user has the right to access all of its forms
* @param user The requesting user
* @param quizzFormId The ID of the quizz we want to get
* @return The Quizz database object
* @throws APIException If something isn't right
*/
@NotNull
public Quizz getQuizz4Watch(User user, long quizzId) throws APIException {
if (user == null)
throw LOGIN_REQUIRED_EXCEPTION;
Optional<Quizz> oquizz = qRepository.findById(quizzId);
if (!oquizz.isPresent())
throw new APIException("Could not find quizz with id %d".formatted(quizzId), HttpStatus.NOT_FOUND);
Quizz q = oquizz.get();
if (!(q.getOwner().equals(user) || uService.hasPrivilege(user, Privilege.VIEW_ALL_FORMS)))
throw new APIException((
"User %s cannot access all forms of quizz %d." +
"Must be either author of the quizz, or have VIEW_ALL_FORMS privilege")
.formatted(user.getName(), q.getId()));
return q;
}
/**
* Gets the specified quizz form if user has right to access it and if the quizz form is complete
* @param user The requesting user
* @param quizzFormId The ID of the quizz form we want to get
* @return The QuizzForm database object
* @throws APIException If something isn't right
*/
@NotNull
public QuizzForm getQuizzForm(User user, long quizzFormId) throws APIException {
if (user == null)
throw LOGIN_REQUIRED_EXCEPTION;
Optional<QuizzForm> oqf = qfRepository.findById(quizzFormId);
if(oqf.isEmpty())
throw new APIException("Could not find quizz form with id %d".formatted(quizzFormId), HttpStatus.NOT_FOUND);
QuizzForm qf = oqf.get();
if(!qf.isDone())
throw new APIException("Cannot access a quizz form that is not done");
if(!(
qf.getUser().equals(user) ||
qf.getQuizz().getOwner().equals(user) ||
uService.hasPrivilege(user, Privilege.VIEW_ALL_FORMS)
)) throw new APIException((
"User %s cannot access quizz form %d." +
"Must be either author of the form or author of the quizz, or have VIEW_ALL_FORMS privilege")
.formatted(user.getName(), qf.getId()));
return qf;
}
/**
* Gets the question and checks it is associated with the given quizz
* @param quizz The given quizz to check the question against
* @param questionId The ID of the question we want to get
* @return The Question database object
* @throws APIException If something isn't right
*/
@NotNull
private Question getQuestion(Quizz quizz, long questionId) throws APIException{
Optional<Question> oq = questionRepository.findById(questionId);
if(oq.isEmpty())
throw new APIException("Could not find question with id %d".formatted(questionId));
Question q = oq.get();
if(!q.getQuizz().equals(quizz))
throw new APIException("Question is not associated with the right quizzId (got %d, expected %d)"
.formatted(q.getQuizz().getId(), quizz.getId()));
return q;
}
@Override @Override
public JsonNode getQuizzInfo(User user, long quizzId) { public JsonNode getQuizzInfo(User user, long quizzId) throws APIException{
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId); if(user == null)
if(authCheck.isPresent()) return authCheck.get(); throw LOGIN_REQUIRED_EXCEPTION;
Quizz quizz = getQuizz4Edit(user, quizzId);
Quizz quizz = qRepository.findById(quizzId).get();
ArrayNode n = JsonNodeFactory.instance.arrayNode(quizz.getQuestionCount()); ArrayNode n = JsonNodeFactory.instance.arrayNode(quizz.getQuestionCount());
for(int i = 0;i<quizz.getQuestionCount();i++)n.add(JsonNodeFactory.instance.nullNode()); for(int i = 0;i<quizz.getQuestionCount();i++)n.add(JsonNodeFactory.instance.nullNode());
@@ -301,13 +366,11 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public JsonNode setQuizzName(User user, long quizzId, String newName) { public JsonNode setQuizzName(User user, long quizzId, String newName) {
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId);
if(authCheck.isPresent()) return authCheck.get(); Quizz quizz = getQuizz4Edit(user, quizzId);
if(newName.isBlank() || newName.length()>255) if(newName.isBlank() || newName.length()>255)
return errorNode("Le nom est invalide"); throw new APIException("Le nom est invalide");
Quizz quizz = qRepository.findById(quizzId).get();
quizz.setName(newName); quizz.setName(newName);
ObjectNode out = JsonNodeFactory.instance.objectNode(); ObjectNode out = JsonNodeFactory.instance.objectNode();
@@ -318,11 +381,8 @@ public class QuizzManagerImpl implements QuizzManager {
public static final QTypes DEFAULT_QTYPE = QTypes.DCC; public static final QTypes DEFAULT_QTYPE = QTypes.DCC;
@Override @Override
public JsonNode addQuestion(User user, long quizzId) { public JsonNode addQuestion(User user, long quizzId) throws APIException {
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId); Quizz quizz = getQuizz4Edit(user, quizzId);
if(authCheck.isPresent()) return authCheck.get();
Quizz quizz = qRepository.findById(quizzId).get();
Question q = new Question(); Question q = new Question();
q.setType(DEFAULT_QTYPE); q.setType(DEFAULT_QTYPE);
@@ -342,18 +402,9 @@ public class QuizzManagerImpl implements QuizzManager {
} }
@Override @Override
public JsonNode removeQuestion(User user, long quizzId, long questionId) { public JsonNode removeQuestion(User user, long quizzId, long questionId) throws APIException {
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId); Quizz quizz = getQuizz4Edit(user, quizzId);
if(authCheck.isPresent()) return authCheck.get(); Question q = getQuestion(quizz, questionId);
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 -> { questionRepository.findByQuizz(quizz).forEach(qq -> {
if(qq.getIndex()>q.getIndex()) if(qq.getIndex()>q.getIndex())
@@ -368,30 +419,21 @@ public class QuizzManagerImpl implements QuizzManager {
} }
@Override @Override
public JsonNode reorderQuestions(User user, long quizzId, List<Long> newOrder) { public JsonNode reorderQuestions(User user, long quizzId, List<Long> newOrder) throws APIException {
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId); Quizz quizz = getQuizz4Edit(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 // We need that the base set of neworder is the list of ids
// 1) The length is right // 1) The length is right
if(quizz.getQuestionCount() != newOrder.size()) if(quizz.getQuestionCount() != newOrder.size())
return errorNode("You must put every question in order"); throw new APIException("You must put every question in order");
// 2) There is no duplicates // 2) There is no duplicates
if(new HashSet<Long>(newOrder).size() != newOrder.size()) if(new HashSet<Long>(newOrder).size() != newOrder.size())
return errorNode("You shouldn't put duplicates in the new order"); throw new APIException("You shouldn't put duplicates in the new order");
// 3) All ids correspond to an actual question of the right quizz // 3) All ids correspond to an actual question of the right quizz
List<Question> questions = new ArrayList<>(newOrder.size()); List<Question> questions = new ArrayList<>(newOrder.size());
for(int i = 0;i<newOrder.size();i++){ for(int i = 0;i<newOrder.size();i++){
try { Question q = getQuestion(quizz, newOrder.get(i));
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); questions.add(q);
} catch (EntityNotFoundException e){
return errorNode("Could not find question with id "+newOrder.get(i));
}
} }
// All Checks passed // All Checks passed
@@ -406,23 +448,11 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public JsonNode editQuestion(User user, long quizzId, long questionId, JsonNode value) { public JsonNode editQuestion(User user, long quizzId, long questionId, JsonNode value) {
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId); Quizz quizz = getQuizz4Edit(user, quizzId);
if(authCheck.isPresent()) return authCheck.get(); Question q = getQuestion(quizz, questionId);
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)) if(!q.getType().validate(value))
return errorNode("Invalid question value"); throw new APIException("Invalid question value");
q.setValue(value); q.setValue(value);
ObjectNode out = JsonNodeFactory.instance.objectNode(); ObjectNode out = JsonNodeFactory.instance.objectNode();
@@ -432,20 +462,8 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public JsonNode setQuestionType(User user, long quizzId, long questionId, QTypes type) { public JsonNode setQuestionType(User user, long quizzId, long questionId, QTypes type) {
Optional<JsonNode> authCheck = checkEditQuizz(user, quizzId); Quizz quizz = getQuizz4Edit(user, quizzId);
if(authCheck.isPresent()) return authCheck.get(); Question q = getQuestion(quizz, questionId);
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) // If type is the same, we don't change (and dont reset the value)
JsonNode n; JsonNode n;
@@ -464,33 +482,9 @@ public class QuizzManagerImpl implements QuizzManager {
return out; return out;
} }
@Override
public Optional<QuizzForm> canViewQuizzForm(User user, long quizzFormId) {
Optional<QuizzForm> oqf = qfRepository.findById(quizzFormId);
if(oqf.isEmpty()) return oqf;
QuizzForm qf = oqf.get();
if(!qf.isDone()) return Optional.empty();
if(qf.getUser().equals(user)) return oqf;
if(uService.hasPrivilege(user, Privilege.VIEW_ALL_FORMS)) return oqf;
return Optional.empty();
}
@Override
public Optional<Quizz> canViewQuizzFormsOfQuizz(User user, long quizzId) {
Optional<Quizz> oq = qRepository.findById(quizzId);
if(oq.isEmpty()) return oq;
Quizz q = oq.get();
if(q.getOwner().equals(user)) return oq;
if(uService.hasPrivilege(user, Privilege.VIEW_ALL_FORMS)) return oq;
return Optional.empty();
}
@Override @Override
public JsonNode getQuizzFormData(User user, long quizzFormId) { public JsonNode getQuizzFormData(User user, long quizzFormId) {
Optional<QuizzForm> oqf = canViewQuizzForm(user, quizzFormId); QuizzForm form = getQuizzForm(user, quizzFormId);
if(oqf.isEmpty())
return errorNode("Could not access the quizzform"); //TODO more precise error node
QuizzForm form = oqf.get();
List<Question> questions = questionRepository.findByQuizzOrderByIndexAsc(form.getQuizz()); List<Question> questions = questionRepository.findByQuizzOrderByIndexAsc(form.getQuizz());
List<Answer> answers = answerRepository.findByFormAndQuestionIn(form, questions); List<Answer> answers = answerRepository.findByFormAndQuestionIn(form, questions);
assert questions.size() == answers.size(); assert questions.size() == answers.size();
@@ -512,16 +506,14 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public JsonNode getAllFormsData(User u, long quizzId) { public JsonNode getAllFormsData(User u, long quizzId) {
Optional<Quizz> oq = canViewQuizzFormsOfQuizz(u, quizzId); Quizz quizz = getQuizz4Watch(u, quizzId);
if(oq.isEmpty())
return errorNode("Could not access the quizzform"); //TODO more precise error node
Quizz quizz = oq.get();
ObjectNode out = JsonNodeFactory.instance.objectNode(); ObjectNode out = JsonNodeFactory.instance.objectNode();
out.set("id",JsonNodeFactory.instance.numberNode(quizz.getId())); out.set("id",JsonNodeFactory.instance.numberNode(quizz.getId()));
out.set("name",JsonNodeFactory.instance.textNode(quizz.getName())); out.set("name",JsonNodeFactory.instance.textNode(quizz.getName()));
ArrayNode questionNode = JsonNodeFactory.instance.arrayNode(quizz.getQuestionCount()); ArrayNode questionNode = JsonNodeFactory.instance.arrayNode(quizz.getQuestionCount());
List<Question> questions = quizz.getQuestions().stream().sorted((p,q) -> Integer.valueOf(p.getIndex()).compareTo(q.getIndex())).toList(); List<Question> questions = quizz.getQuestions().stream().sorted((p,q) -> Integer.compare(p.getIndex(),q.getIndex())).toList();
for(int i=0;i<questions.size();i++) { for(int i=0;i<questions.size();i++) {
ObjectNode qNode = JsonNodeFactory.instance.objectNode(); ObjectNode qNode = JsonNodeFactory.instance.objectNode();
qNode.set("id", JsonNodeFactory.instance.numberNode(questions.get(i).getId())); qNode.set("id", JsonNodeFactory.instance.numberNode(questions.get(i).getId()));
@@ -556,10 +548,7 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public JsonNode getQuizzFormAdvancments(User user, long quizzId) { public JsonNode getQuizzFormAdvancments(User user, long quizzId) {
Optional<Quizz> oq = canViewQuizzFormsOfQuizz(user, quizzId); Quizz quizz = getQuizz4Watch(user, quizzId);
if(oq.isEmpty())
return errorNode("Could not access the forms for this quizz"); //TODO more precise error node
Quizz quizz = oq.get();
List<QuizzForm> quizzForms = qfRepository.findByQuizz(quizz); List<QuizzForm> quizzForms = qfRepository.findByQuizz(quizz);
quizzForms.sort((qfa,qfb) -> qfa.getUser().getName().compareTo(qfb.getUser().getName())); quizzForms.sort((qfa,qfb) -> qfa.getUser().getName().compareTo(qfb.getUser().getName()));
@@ -585,15 +574,10 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public Quizz duplicateQuizz(User u, long quizzId) { public Quizz duplicateQuizz(User u, long quizzId) {
if (!canEditQuizz(u, quizzId)) Quizz q = getQuizz4Edit(u, quizzId);
return null;
Optional<Quizz> oq = qRepository.findById(quizzId);
// CHECKED BEFORE if (oq.isEmpty()) return null;
Quizz q = oq.get();
Quizz nq = new Quizz(); Quizz nq = new Quizz();
nq.setName(q.getName() + "("+ Integer.toHexString((int)(Math.random()*0xFFFFFFF)) +")"); nq.setName(q.getName() + "("+ Integer.toHexString(rand.nextInt(0xFFFFFFF)) +")");
nq.setOwner(q.getOwner()); nq.setOwner(q.getOwner());
nq.setQuestionCount(q.getQuestionCount()); nq.setQuestionCount(q.getQuestionCount());
nq.setPublicQuestionCount(q.getPublicQuestionCount()); nq.setPublicQuestionCount(q.getPublicQuestionCount());
@@ -615,12 +599,7 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public boolean markComplete(User u, long quizzId) { public boolean markComplete(User u, long quizzId) {
if (!canEditQuizz(u, quizzId)) Quizz q = getQuizz4Edit(u, quizzId);
return false;
Optional<Quizz> oq = qRepository.findById(quizzId);
if(oq.isEmpty())
return false;
Quizz q = oq.get();
q.setPublicQuestionCount(null); q.setPublicQuestionCount(null);
q.setComplete(true); q.setComplete(true);
@@ -631,12 +610,7 @@ public class QuizzManagerImpl implements QuizzManager {
@Override @Override
public boolean setPublicQuestionCount(User u, long quizzId, Integer publicQuestionCount) { public boolean setPublicQuestionCount(User u, long quizzId, Integer publicQuestionCount) {
if (!canEditQuizz(u, quizzId)) Quizz q = getQuizz4Watch(u, quizzId);
return false;
Optional<Quizz> oq = qRepository.findById(quizzId);
if(oq.isEmpty())
return false;
Quizz q = oq.get();
if(!q.isComplete()) if(!q.isComplete())
return false; return false;
if(publicQuestionCount != null && (publicQuestionCount > q.getQuestionCount() || publicQuestionCount < 0)) if(publicQuestionCount != null && (publicQuestionCount > q.getQuestionCount() || publicQuestionCount < 0))
@@ -1,13 +1,14 @@
package com.bernard.misael.thecrew; package com.bernard.misael.thecrew;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class TheCrewConfig implements WebMvcConfigurer { public class TheCrewConfig implements WebMvcConfigurer {
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
String imgFolder = System.getenv("THECREW_IMAGES_FOLDER"); String imgFolder = System.getenv("THECREW_IMAGES_FOLDER");
registry.addResourceHandler("/thecrew/**") registry.addResourceHandler("/thecrew/**")
@@ -1,10 +1,9 @@
package com.bernard.misael.thecrew; package com.bernard.misael.thecrew;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
+1 -1
View File
@@ -53,7 +53,7 @@
function next() { function next() {
$.ajax({ $.ajax({
url: "/questions/question/"+qid, url: "/questions/question/"+qid,
type: "GET", type: "POST",
dataType: "json", dataType: "json",
success: function(res) { success: function(res) {
console.log(res) console.log(res)
+2 -1
View File
@@ -421,9 +421,10 @@
if (confirm("Voulez-vous vraiment marquer ce quizz comme terminé ? Vous ne pourrez plus l'éditer !")) { if (confirm("Voulez-vous vraiment marquer ce quizz comme terminé ? Vous ne pourrez plus l'éditer !")) {
$.ajax({ $.ajax({
url: "/questions/mark-complete/"+quizzid, url: "/questions/mark-complete/"+quizzid,
type: "GET", type: "POST",
success: function(res) { success: function(res) {
console.log("SUPER") console.log("SUPER")
//TODO redirect to the right page
} }
}) })
} }
+4 -1
View File
@@ -153,6 +153,9 @@
function setPublicQuestionCount(n) { function setPublicQuestionCount(n) {
return function (e) { setPQC(n) } return function (e) { setPQC(n) }
} }
function setPublicQuestionCountLast(n) {
return function (e) { setPQC(qCount) }
}
function setPublicQuestionCountRelative(n) { function setPublicQuestionCountRelative(n) {
return function (e) { setPQC(pqc + n) } return function (e) { setPQC(pqc + n) }
@@ -164,7 +167,7 @@
$('#set-question-begin').on('click',setPublicQuestionCount(0)) $('#set-question-begin').on('click',setPublicQuestionCount(0))
$('#set-question-decrement').on('click',setPublicQuestionCountRelative(-1)) $('#set-question-decrement').on('click',setPublicQuestionCountRelative(-1))
$('#set-question-increment').on('click',setPublicQuestionCountRelative(+1)) $('#set-question-increment').on('click',setPublicQuestionCountRelative(+1))
$('#set-question-end').on('click',setPublicQuestionCount(qCount)) $('#set-question-last').on('click',setPublicQuestionCountLast())
getdata() getdata()
</script> </script>
</main> </main>