Compare commits
17
Commits
c62f070e39
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
48fbc434a4 | ||
|
|
430488ceb8 | ||
|
|
5131281613 | ||
|
|
56070c240f | ||
|
|
367677c7a8 | ||
|
|
5836157c57 | ||
|
|
538231dbd4 | ||
|
|
6de4e43f8d | ||
|
|
270025a1b4
|
||
|
|
655971a000
|
||
|
|
6e5c41431a
|
||
|
|
bbe43cef95
|
||
|
|
d411d41cff
|
||
|
|
08262f3dfd
|
||
|
|
225b5df774
|
||
|
|
c6fa363cd9
|
||
|
|
33d1f1871a
|
@@ -4,6 +4,9 @@ build/
|
|||||||
!gradle/wrapper/gradle-wrapper.jar
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
!**/src/main/**/build/
|
!**/src/main/**/build/
|
||||||
!**/src/test/**/build/
|
!**/src/test/**/build/
|
||||||
|
.postgres
|
||||||
|
/db-migration.sql
|
||||||
|
/thecrew-missions.json
|
||||||
|
|
||||||
### STS ###
|
### STS ###
|
||||||
.apt_generated
|
.apt_generated
|
||||||
|
|||||||
@@ -11,3 +11,30 @@ And finaly we run `./gradlew bootRun`
|
|||||||
The compose file is enough.
|
The compose file is enough.
|
||||||
|
|
||||||
`launcher.sh` checks that the migrations are applied and then runs the spring boot server on port 8080
|
`launcher.sh` checks that the migrations are applied and then runs the spring boot server on port 8080
|
||||||
|
|
||||||
|
|
||||||
|
## OIDC
|
||||||
|
|
||||||
|
In order to connect to oidc, you need to setup the following options, if `bcom` is your provider name:
|
||||||
|
```Yaml
|
||||||
|
spring:
|
||||||
|
security:
|
||||||
|
oauth2:
|
||||||
|
client:
|
||||||
|
activate: true
|
||||||
|
registration:
|
||||||
|
bcom:
|
||||||
|
provider: bcom
|
||||||
|
client-id: misael
|
||||||
|
client-secret: "myclientsecret"
|
||||||
|
authorization-grant-type: authorization_code
|
||||||
|
scope: openid,roles
|
||||||
|
provider:
|
||||||
|
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.
|
||||||
@@ -36,6 +36,7 @@ dependencies {
|
|||||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:_'
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:_'
|
||||||
implementation Spring.boot.web
|
implementation Spring.boot.web
|
||||||
implementation 'org.springframework:spring-jdbc:_'
|
implementation 'org.springframework:spring-jdbc:_'
|
||||||
|
implementation Spring.boot.oauth2Client
|
||||||
implementation 'org.flywaydb:flyway-core:_'
|
implementation 'org.flywaydb:flyway-core:_'
|
||||||
implementation 'org.flywaydb:flyway-database-postgresql:_'
|
implementation 'org.flywaydb:flyway-database-postgresql:_'
|
||||||
implementation Spring.session.jdbc
|
implementation Spring.session.jdbc
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ services:
|
|||||||
- 'POSTGRES_DB=misael'
|
- 'POSTGRES_DB=misael'
|
||||||
- 'POSTGRES_PASSWORD=misael-dev'
|
- 'POSTGRES_PASSWORD=misael-dev'
|
||||||
- 'POSTGRES_USER=misael'
|
- 'POSTGRES_USER=misael'
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
networks:
|
networks:
|
||||||
- misanetwork
|
- misanetwork
|
||||||
|
|
||||||
|
|||||||
Generated
+147
@@ -0,0 +1,147 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"build-gradle-application": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-parts": "flake-parts",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1761837331,
|
||||||
|
"narHash": "sha256-uqmuTLq3VJfPqD1frbzIv9EcmlKcl7pRjNFwKkWIr6I=",
|
||||||
|
"ref": "refs/heads/main",
|
||||||
|
"rev": "411fff172011aafc7b2afc365ced450ffe7d729d",
|
||||||
|
"revCount": 98,
|
||||||
|
"type": "git",
|
||||||
|
"url": "file:/home/mysaa/Documents/Projets/buildGradleApplication"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "file:/home/mysaa/Documents/Projets/buildGradleApplication"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-parts": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs-lib": "nixpkgs-lib"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1756770412,
|
||||||
|
"narHash": "sha256-+uWLQZccFHwqpGqr2Yt5VsW/PbeJVTn9Dk6SHWhNRPw=",
|
||||||
|
"owner": "hercules-ci",
|
||||||
|
"repo": "flake-parts",
|
||||||
|
"rev": "4524271976b625a4a605beefd893f270620fd751",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"id": "flake-parts",
|
||||||
|
"type": "indirect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flake-utils": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1605370193,
|
||||||
|
"narHash": "sha256-YyMTf3URDL/otKdKgtoMChu4vfVL3vCMkRqpGifhUn0=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "5021eac20303a61fafe17224c087f5519baed54d",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gradle2nix": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs_2"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1755902591,
|
||||||
|
"narHash": "sha256-mnPaPH9k6Mbr7O0KzBBdkiDDS88oB5NiFHVSFkCzswU=",
|
||||||
|
"owner": "tadfisher",
|
||||||
|
"repo": "gradle2nix",
|
||||||
|
"rev": "30cfe5889188524223364ee7919d94e83d6ee44a",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "tadfisher",
|
||||||
|
"ref": "v2",
|
||||||
|
"repo": "gradle2nix",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1758035966,
|
||||||
|
"narHash": "sha256-qqIJ3yxPiB0ZQTT9//nFGQYn8X/PBoJbofA7hRKZnmE=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "8d4ddb19d03c65a36ad8d189d001dc32ffb0306b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs-lib": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1754788789,
|
||||||
|
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-community",
|
||||||
|
"repo": "nixpkgs.lib",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_2": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763678758,
|
||||||
|
"narHash": "sha256-+hBiJ+kG5IoffUOdlANKFflTT5nO3FrrR2CA3178Y5s=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "117cc7f94e8072499b0a7aa4c52084fa4e11cc9b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs_3": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1763618868,
|
||||||
|
"narHash": "sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r+JerayK/4wvdWA=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "a8d610af3f1a5fb71e23e08434d8d61a466fc942",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixpkgs-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"build-gradle-application": "build-gradle-application",
|
||||||
|
"gradle2nix": "gradle2nix",
|
||||||
|
"nixpkgs": "nixpkgs_3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
description = "Misael server";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||||
|
|
||||||
|
build-gradle-application.url = "git+file:/home/mysaa/Documents/Projets/buildGradleApplication";#"github:raphiz/buildGradleApplication";
|
||||||
|
|
||||||
|
gradle2nix.url = "github:tadfisher/gradle2nix/v2";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { self, nixpkgs, build-gradle-application, gradle2nix, ... }@inputs:
|
||||||
|
let system = "x86_64-linux";
|
||||||
|
pkgs = import nixpkgs { inherit system; overlays = [ build-gradle-application.overlays.default ]; };
|
||||||
|
lib = pkgs.lib;
|
||||||
|
jdk = pkgs.openjdk21;
|
||||||
|
gradle = pkgs.gradle;
|
||||||
|
|
||||||
|
misael = gradle2nix.builders.x86_64-linux.buildGradlePackage {
|
||||||
|
pname = "misael";
|
||||||
|
version = "1.0";
|
||||||
|
lockFile = ./gradle.lock;
|
||||||
|
gradleInstallFlags = [ "assemble" ];
|
||||||
|
inherit gradle;
|
||||||
|
buildJdk = jdk;
|
||||||
|
src = ./.;
|
||||||
|
buildPhase = ''
|
||||||
|
gradle assemble
|
||||||
|
'';
|
||||||
|
installPhase = ''
|
||||||
|
mkdir $out
|
||||||
|
cp ./build/libs/misael-beta.jar $out/misael.jar
|
||||||
|
cp -r ./build/resources/ $out/resources/
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
misael-launcher = pkgs.writeShellScriptBin "misael" ''
|
||||||
|
set -e
|
||||||
|
echo "Checking Database Migration"
|
||||||
|
${pkgs.flyway}/bin/flyway "-url=$MISAEL_DATABASE" -user=misael "-password=$MISAEL_PASSWORD" -locations="filesystem:${misael}/resources/main/db/migration/" -schemas=misael migrate
|
||||||
|
|
||||||
|
export THECREW_IMAGES_FOLDER=${thecrew-images}
|
||||||
|
echo "Launching misael"
|
||||||
|
${jdk}/bin/java -jar ${misael}/misael.jar --spring.datasource.url=$MISAEL_DATABASE --spring.datasource.password=$MISAEL_PASSWORD "$@"
|
||||||
|
'';
|
||||||
|
postgres-server = pkgs.writeShellScriptBin "postgres-server" ''
|
||||||
|
mkdir -p .postgres
|
||||||
|
${pkgs.podman}/bin/podman run -e POSTGRES_USER=misael -e POSTGRES_PASSWORD=misael-dev -e POSTGRES_DB=misael --volume ./.postgres:/var/lib/postgresql -p 127.0.0.1:5432:5432 postgres:18.4 &> .postgres/postgres.log &
|
||||||
|
'';
|
||||||
|
thecrew-images = pkgs.runCommand "thecrew-images" {} ''
|
||||||
|
mkdir $out
|
||||||
|
cd $out
|
||||||
|
export PATH=${pkgs.inkscape}/bin:$PATH
|
||||||
|
${lib.getExe pkgs.python3} ${./src/main/resources/static/images/thecrew-make.py}\
|
||||||
|
${./src/main/resources/static/images/thecrew.svg}\
|
||||||
|
${./src/main/resources/static/images/thecrew-modifiers.svg}
|
||||||
|
'';
|
||||||
|
in {
|
||||||
|
packages.${system} = {
|
||||||
|
default = self.packages.${system}.misael;
|
||||||
|
inherit thecrew-images misael;
|
||||||
|
};
|
||||||
|
|
||||||
|
apps.${system} = {
|
||||||
|
default = self.apps.${system}.misael;
|
||||||
|
misael = { type = "app"; program = "${misael-launcher}/bin/misael"; };
|
||||||
|
postgres-server = { type = "app"; program = "${postgres-server}/bin/postgres-server"; };
|
||||||
|
};
|
||||||
|
|
||||||
|
devShells.${system} = {
|
||||||
|
default = pkgs.mkShell {
|
||||||
|
packages = [
|
||||||
|
jdk
|
||||||
|
gradle
|
||||||
|
(pkgs.vscode-with-extensions.override {
|
||||||
|
vscode = pkgs.vscodium;
|
||||||
|
vscodeExtensions = with pkgs.vscode-extensions; [
|
||||||
|
redhat.java
|
||||||
|
vscjava.vscode-java-debug
|
||||||
|
vscjava.vscode-java-test
|
||||||
|
vscjava.vscode-gradle
|
||||||
|
vscjava.vscode-java-dependency
|
||||||
|
sonarsource.sonarlint-vscode
|
||||||
|
bbenoist.nix
|
||||||
|
redhat.vscode-yaml
|
||||||
|
];
|
||||||
|
})
|
||||||
|
];
|
||||||
|
shellHook = ''
|
||||||
|
echo "Starting Gradle daemon ..."
|
||||||
|
gradle
|
||||||
|
export THECREW_IMAGES_FOLDER=${thecrew-images}
|
||||||
|
export THECREW_MISSIONS_FILE=$PWD/thecrew-missions.json
|
||||||
|
echo "Gradle daemon started."
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
formatter.${system} = pkgs.nixpkgs-fmt;
|
||||||
|
};
|
||||||
|
}
|
||||||
+2836
File diff suppressed because it is too large
Load Diff
@@ -1,60 +0,0 @@
|
|||||||
package com.bernard.misael;
|
|
||||||
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|
||||||
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableMethodSecurity(securedEnabled = true)
|
|
||||||
public class SpringSecurity {
|
|
||||||
|
|
||||||
public static final Logger LOG = Logger.getLogger(SpringSecurity.class.getName());
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserDetailsService userDetailsService;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public static PasswordEncoder passwordEncoder(){
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
||||||
http.csrf(csrf -> csrf.disable())
|
|
||||||
.authorizeHttpRequests((authorize) ->
|
|
||||||
authorize
|
|
||||||
.requestMatchers("/**").permitAll()
|
|
||||||
).formLogin(
|
|
||||||
form -> form
|
|
||||||
.loginPage("/login")
|
|
||||||
.loginProcessingUrl("/login")
|
|
||||||
.defaultSuccessUrl("/")
|
|
||||||
.permitAll()
|
|
||||||
).logout(
|
|
||||||
logout -> logout
|
|
||||||
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
|
||||||
.permitAll()
|
|
||||||
);
|
|
||||||
return http.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
|
||||||
auth
|
|
||||||
.userDetailsService(userDetailsService)
|
|
||||||
.passwordEncoder(passwordEncoder());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+32
-27
@@ -1,12 +1,10 @@
|
|||||||
package com.bernard.misael.web;
|
package com.bernard.misael.auth;
|
||||||
|
|
||||||
import java.security.Principal;
|
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.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
import org.springframework.ui.Model;
|
import org.springframework.ui.Model;
|
||||||
import org.springframework.validation.BindingResult;
|
import org.springframework.validation.BindingResult;
|
||||||
@@ -15,11 +13,11 @@ import org.springframework.web.bind.annotation.ModelAttribute;
|
|||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
import com.bernard.misael.model.Role;
|
import com.bernard.misael.auth.repository.UserRepository;
|
||||||
import com.bernard.misael.model.User;
|
import com.bernard.misael.auth.service.UserService;
|
||||||
import com.bernard.misael.repository.UserRepository;
|
import com.bernard.misael.auth.model.Privilege;
|
||||||
import com.bernard.misael.service.UserService;
|
import com.bernard.misael.auth.model.User;
|
||||||
import com.bernard.misael.service.dto.UserDto;
|
import com.bernard.misael.auth.service.dto.UserDto;
|
||||||
|
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -27,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")
|
||||||
@@ -41,21 +38,8 @@ public class AuthController {
|
|||||||
return "login";
|
return "login";
|
||||||
}
|
}
|
||||||
|
|
||||||
public User getLoggedInUser() {
|
|
||||||
if(SecurityContextHolder.getContext().getAuthentication().getPrincipal()
|
|
||||||
instanceof org.springframework.security.core.userdetails.User){
|
|
||||||
org.springframework.security.core.userdetails.User user
|
|
||||||
= (org.springframework.security.core.userdetails.User)
|
|
||||||
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
|
||||||
return userService.findUserByName(user.getUsername());
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/")
|
@GetMapping("/")
|
||||||
public String index(Model model) {
|
public String index(Model model) {
|
||||||
|
|
||||||
return "index";
|
return "index";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -108,19 +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 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.pseudo = u.getName();
|
this.pseudo = u.getName();
|
||||||
this.roles = u.getRoles().stream().map(Role::getName).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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+4
-17
@@ -1,18 +1,12 @@
|
|||||||
package com.bernard.misael;
|
package com.bernard.misael.auth;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Function;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.bernard.misael.model.Privilege;
|
import com.bernard.misael.auth.model.User;
|
||||||
import com.bernard.misael.model.Role;
|
import com.bernard.misael.auth.repository.UserRepository;
|
||||||
import com.bernard.misael.model.User;
|
|
||||||
import com.bernard.misael.repository.UserRepository;
|
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class CustomUserDetailsService implements UserDetailsService {
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
@@ -28,16 +22,9 @@ public class CustomUserDetailsService implements UserDetailsService {
|
|||||||
User user = userRepository.findByName(pseudo);
|
User user = userRepository.findByName(pseudo);
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
Stream<Privilege> inducedPrivileges = user.getRoles().stream()
|
|
||||||
.map(Role::getPrivileges)
|
|
||||||
.map(Set<Privilege>::stream)
|
|
||||||
.flatMap(Function.identity())
|
|
||||||
.sorted()
|
|
||||||
.distinct();
|
|
||||||
Stream<Role> roles = user.getRoles().stream();
|
|
||||||
return new org.springframework.security.core.userdetails.User(user.getName(),
|
return new org.springframework.security.core.userdetails.User(user.getName(),
|
||||||
user.getPassword(),
|
user.getPassword(),
|
||||||
Stream.concat(inducedPrivileges, roles).toList()
|
user.getPrivileges()
|
||||||
);
|
);
|
||||||
}else{
|
}else{
|
||||||
throw new UsernameNotFoundException("Invalid username or password.");
|
throw new UsernameNotFoundException("Invalid username or password.");
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.bernard.misael.auth;
|
||||||
|
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.env.Environment;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||||
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
import com.bernard.misael.auth.service.UserService;
|
||||||
|
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(securedEnabled = true)
|
||||||
|
public class SpringSecurity {
|
||||||
|
|
||||||
|
public static final Logger LOG = Logger.getLogger(SpringSecurity.class.getName());
|
||||||
|
|
||||||
|
public static final boolean oidcActivated(Environment env) {
|
||||||
|
String activate = env.getProperty("spring.security.oauth2.client.activate");
|
||||||
|
return (activate!=null) && activate.equalsIgnoreCase("true");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public static PasswordEncoder passwordEncoder(){
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain filterChain(HttpSecurity http, Environment env, final UserService us) throws Exception {
|
||||||
|
http.csrf(csrf -> csrf.disable());
|
||||||
|
http.authorizeHttpRequests(authorize ->
|
||||||
|
authorize
|
||||||
|
.requestMatchers("/**").permitAll()
|
||||||
|
);
|
||||||
|
if(oidcActivated(env)) {
|
||||||
|
http.oauth2Login(oauth2 -> oauth2.
|
||||||
|
userInfoEndpoint(userInfo -> userInfo.
|
||||||
|
oidcUserService(this.oidcUserService(us))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
http.formLogin(
|
||||||
|
form -> form
|
||||||
|
.loginPage("/login")
|
||||||
|
.loginProcessingUrl("/login")
|
||||||
|
.defaultSuccessUrl("/")
|
||||||
|
.permitAll()
|
||||||
|
);
|
||||||
|
http.logout(
|
||||||
|
logout -> logout
|
||||||
|
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
||||||
|
.permitAll()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth, UserDetailsService userDetailsService) throws Exception {
|
||||||
|
auth
|
||||||
|
.userDetailsService(userDetailsService)
|
||||||
|
.passwordEncoder(passwordEncoder());
|
||||||
|
}
|
||||||
|
|
||||||
|
private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService(UserService us) {
|
||||||
|
final OidcUserService delegate = new OidcUserService();
|
||||||
|
|
||||||
|
return userRequest -> {
|
||||||
|
// We create the original user
|
||||||
|
OidcUser oidcUser = delegate.loadUser(userRequest);
|
||||||
|
User u = us.ensureUser(oidcUser);
|
||||||
|
return new DefaultOidcUser(u.getPrivileges(), oidcUser.getIdToken());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.bernard.misael.auth.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public enum Privilege implements GrantedAuthority {
|
||||||
|
|
||||||
|
LIST_USERS,ADD_USERS,LIST_QUIZZ,CREATE_QUIZZ,VIEW_ALL_FORMS;
|
||||||
|
|
||||||
|
public static final Map<String,Privilege> oidcRoles = Map.of(
|
||||||
|
"misael-view-all-forms",VIEW_ALL_FORMS,
|
||||||
|
"misael-list-users",LIST_USERS,
|
||||||
|
"misael-create-quizz",CREATE_QUIZZ,
|
||||||
|
"misael-list-quizz", LIST_QUIZZ
|
||||||
|
);
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return this.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Privilege ofOidcString(String s) {
|
||||||
|
return oidcRoles.getOrDefault(s, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+17
-13
@@ -1,19 +1,20 @@
|
|||||||
package com.bernard.misael.model;
|
package com.bernard.misael.auth.model;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import jakarta.persistence.CascadeType;
|
import com.bernard.misael.quizz.model.Quizz;
|
||||||
|
|
||||||
|
import jakarta.persistence.CollectionTable;
|
||||||
import jakarta.persistence.Column;
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.ElementCollection;
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EnumType;
|
||||||
|
import jakarta.persistence.Enumerated;
|
||||||
import jakarta.persistence.FetchType;
|
import jakarta.persistence.FetchType;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
import jakarta.persistence.GenerationType;
|
import jakarta.persistence.GenerationType;
|
||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.JoinTable;
|
|
||||||
import jakarta.persistence.ManyToMany;
|
|
||||||
import jakarta.persistence.OneToMany;
|
import jakarta.persistence.OneToMany;
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -38,15 +39,18 @@ public class User
|
|||||||
@Column(nullable=false,unique = true)
|
@Column(nullable=false,unique = true)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
@Column(nullable=false)
|
@Column(nullable=true, unique = true)
|
||||||
|
private String oidcId;
|
||||||
|
|
||||||
|
@Column(nullable=true)
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
|
@ElementCollection(fetch = FetchType.EAGER)
|
||||||
@JoinTable(
|
@Enumerated(EnumType.STRING)
|
||||||
name="users_roles",
|
@CollectionTable(name = "user_privileges",
|
||||||
joinColumns={@JoinColumn(name="USER_ID", referencedColumnName="ID")},
|
joinColumns = @JoinColumn(name = "id"))
|
||||||
inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
|
@Column(name = "privileges", nullable = false)
|
||||||
private List<Role> roles = new ArrayList<>();
|
private Set<Privilege> privileges;
|
||||||
|
|
||||||
@OneToMany(mappedBy="owner")
|
@OneToMany(mappedBy="owner")
|
||||||
private Set<Quizz> myQuizzs;
|
private Set<Quizz> myQuizzs;
|
||||||
+5
-2
@@ -1,11 +1,14 @@
|
|||||||
package com.bernard.misael.repository;
|
package com.bernard.misael.auth.repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import com.bernard.misael.model.User;
|
import com.bernard.misael.auth.model.User;
|
||||||
|
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
|
||||||
User findByName(String pseudo);
|
User findByName(String pseudo);
|
||||||
|
Optional<User> findByOidcId(String oidcId);
|
||||||
|
|
||||||
}
|
}
|
||||||
+8
-30
@@ -1,7 +1,6 @@
|
|||||||
package com.bernard.misael.service;
|
package com.bernard.misael.auth.service;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Set;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -11,11 +10,9 @@ import org.springframework.lang.NonNull;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import com.bernard.misael.model.Privilege;
|
import com.bernard.misael.auth.model.Privilege;
|
||||||
import com.bernard.misael.model.Role;
|
import com.bernard.misael.auth.model.User;
|
||||||
import com.bernard.misael.model.User;
|
import com.bernard.misael.auth.repository.UserRepository;
|
||||||
import com.bernard.misael.repository.RoleRepository;
|
|
||||||
import com.bernard.misael.repository.UserRepository;
|
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
@@ -28,9 +25,6 @@ public class AdminMaker implements
|
|||||||
@Autowired
|
@Autowired
|
||||||
private UserRepository userRepository;
|
private UserRepository userRepository;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private RoleRepository roleRepository;
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private PasswordEncoder passwordEncoder;
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
@@ -38,37 +32,21 @@ public class AdminMaker implements
|
|||||||
@Transactional
|
@Transactional
|
||||||
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
||||||
|
|
||||||
if (alreadySetup)
|
if (alreadySetup) return;
|
||||||
return;
|
|
||||||
Logger log = Logger.getLogger("AdminMaker");
|
Logger log = Logger.getLogger("AdminMaker");
|
||||||
log.info("Checking that privileges and mysaa user exist");
|
log.info("Checking that privileges and mysaa user exist");
|
||||||
|
|
||||||
Role adminRole = createRoleIfNotFound("ADMIN", EnumSet.allOf(Privilege.class));
|
|
||||||
createRoleIfNotFound("USER", EnumSet.noneOf(Privilege.class));
|
|
||||||
|
|
||||||
User mysaa = userRepository.findByName("mysaa");
|
User mysaa = userRepository.findByName("mysaa");
|
||||||
if (mysaa == null) {
|
if (mysaa == null) {
|
||||||
User user = new User();
|
User user = new User();
|
||||||
user.setName("mysaa");
|
user.setName("mysaa");
|
||||||
user.setPassword(passwordEncoder.encode("super"));
|
user.setPassword(passwordEncoder.encode("super"));
|
||||||
user.setRoles(Arrays.asList(adminRole));
|
user.setPrivileges(Set.of(Privilege.values()));
|
||||||
userRepository.save(user);
|
userRepository.save(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
alreadySetup = true;
|
alreadySetup = true;
|
||||||
log.info("Everything needed has been created");
|
log.info("Everything needed has been created");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
private Role createRoleIfNotFound(
|
|
||||||
String name, EnumSet<Privilege> privileges) {
|
|
||||||
|
|
||||||
Role role = roleRepository.findByName(name);
|
|
||||||
if (role == null) {
|
|
||||||
role = new Role(name);
|
|
||||||
role.setPrivileges(privileges);
|
|
||||||
roleRepository.save(role);
|
|
||||||
}
|
|
||||||
return role;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.bernard.misael.auth.service;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.model.Privilege;
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
import com.bernard.misael.auth.service.dto.UserDto;
|
||||||
|
|
||||||
|
public interface UserService {
|
||||||
|
|
||||||
|
User ofPrincipal(Principal p);
|
||||||
|
|
||||||
|
User ensureUser(OidcUser user);
|
||||||
|
|
||||||
|
void saveUser(UserDto userDto);
|
||||||
|
void changePassword(User user, String password);
|
||||||
|
|
||||||
|
User findUserByName(String name);
|
||||||
|
|
||||||
|
List<UserDto> findAllUsers();
|
||||||
|
|
||||||
|
boolean hasPrivilege(User u, Privilege p);
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
package com.bernard.misael.auth.service;
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.repository.UserRepository;
|
||||||
|
import com.bernard.misael.auth.service.dto.UserDto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.model.Privilege;
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class UserServiceImpl implements UserService {
|
||||||
|
|
||||||
|
private UserRepository userRepository;
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
public UserServiceImpl(UserRepository userRepository,
|
||||||
|
PasswordEncoder passwordEncoder) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.passwordEncoder = passwordEncoder;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NotNull
|
||||||
|
public User ensureUser(OidcUser oidcUser) {
|
||||||
|
String username = oidcUser.getPreferredUsername();
|
||||||
|
String id = oidcUser.getName();
|
||||||
|
Set<Privilege> privileges;
|
||||||
|
try {
|
||||||
|
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'");
|
||||||
|
privileges = resourceAccess
|
||||||
|
.getOrDefault("misael",Map.of())
|
||||||
|
.getOrDefault("roles",List.of())
|
||||||
|
.stream()
|
||||||
|
.map(Privilege::ofOidcString)
|
||||||
|
.filter(p -> p != null)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
} catch (NullPointerException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<User> u = userRepository.findByOidcId(id);
|
||||||
|
if(u.isEmpty()) {
|
||||||
|
Optional<User> uN = Optional.ofNullable(userRepository.findByName(username));
|
||||||
|
if(uN.isEmpty()) {
|
||||||
|
User u0 = new User();
|
||||||
|
u0.setOidcId(id);
|
||||||
|
u0.setName(username);
|
||||||
|
userRepository.save(u0);
|
||||||
|
u = Optional.of(u0);
|
||||||
|
} else {
|
||||||
|
User u0 = uN.get();
|
||||||
|
u0.setOidcId(id);
|
||||||
|
userRepository.save(u0);
|
||||||
|
u = uN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
User theu = u.get();
|
||||||
|
if(!theu.getPrivileges().equals(privileges)) {
|
||||||
|
theu.setPrivileges(privileges);
|
||||||
|
userRepository.save(theu);
|
||||||
|
}
|
||||||
|
return theu;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void saveUser(UserDto userDto) {
|
||||||
|
User user = new User();
|
||||||
|
user.setName(userDto.getName());
|
||||||
|
// encrypt the password using spring security
|
||||||
|
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
|
||||||
|
|
||||||
|
user.setPrivileges(Set.of());
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void changePassword(User user, String password) {
|
||||||
|
user.setPassword(passwordEncoder.encode(password));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User findUserByName(String name) {
|
||||||
|
return userRepository.findByName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<UserDto> findAllUsers() {
|
||||||
|
List<User> users = userRepository.findAll();
|
||||||
|
return users.stream()
|
||||||
|
.map(this::mapToUserDto)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private UserDto mapToUserDto(User user){
|
||||||
|
UserDto userDto = new UserDto();
|
||||||
|
userDto.setName(user.getName());
|
||||||
|
return userDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPrivilege(User u, Privilege p) {
|
||||||
|
return u.getPrivileges().contains(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User ofPrincipal(Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if(p instanceof OAuth2AuthenticationToken o) {
|
||||||
|
u = userRepository.findByOidcId(o.getName()).orElseThrow(() -> new RuntimeException("Oauth2 user was not in the database"));
|
||||||
|
}
|
||||||
|
if(p instanceof UsernamePasswordAuthenticationToken o) {
|
||||||
|
u = userRepository.findByName(o.getName());
|
||||||
|
}
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.service.dto;
|
package com.bernard.misael.auth.service.dto;
|
||||||
|
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
package com.bernard.misael.model;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
|
|
||||||
public enum Privilege implements GrantedAuthority {
|
|
||||||
|
|
||||||
LIST_USERS,ADD_USERS,LIST_QUIZZ;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthority() {
|
|
||||||
return this.name();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package com.bernard.misael.model;
|
|
||||||
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.security.core.GrantedAuthority;
|
|
||||||
|
|
||||||
import jakarta.persistence.CollectionTable;
|
|
||||||
import jakarta.persistence.Column;
|
|
||||||
import jakarta.persistence.ElementCollection;
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
import jakarta.persistence.EnumType;
|
|
||||||
import jakarta.persistence.Enumerated;
|
|
||||||
import jakarta.persistence.FetchType;
|
|
||||||
import jakarta.persistence.GeneratedValue;
|
|
||||||
import jakarta.persistence.GenerationType;
|
|
||||||
import jakarta.persistence.Id;
|
|
||||||
import jakarta.persistence.JoinColumn;
|
|
||||||
import jakarta.persistence.ManyToMany;
|
|
||||||
import jakarta.persistence.Table;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@NoArgsConstructor
|
|
||||||
@AllArgsConstructor
|
|
||||||
@Entity
|
|
||||||
@Table(name="roles")
|
|
||||||
public class Role implements GrantedAuthority
|
|
||||||
{
|
|
||||||
@Id
|
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(nullable=false, unique=true)
|
|
||||||
private String name;
|
|
||||||
|
|
||||||
@ManyToMany(mappedBy="roles")
|
|
||||||
private List<User> users;
|
|
||||||
|
|
||||||
@ElementCollection(fetch = FetchType.EAGER)
|
|
||||||
@Enumerated(EnumType.STRING)
|
|
||||||
@CollectionTable(name = "role_privileges"
|
|
||||||
, joinColumns = @JoinColumn(name = "id"))
|
|
||||||
@Column(name = "privileges", nullable = false)
|
|
||||||
private Set<Privilege> privileges;
|
|
||||||
|
|
||||||
public Role(String name){
|
|
||||||
super();
|
|
||||||
this.setName(name);
|
|
||||||
this.setPrivileges(EnumSet.noneOf(Privilege.class));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getAuthority() {
|
|
||||||
return "ROLE_"+name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
package com.bernard.misael.quizz;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.annotation.Secured;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.service.UserService;
|
||||||
|
import com.bernard.misael.quizz.model.Quizz;
|
||||||
|
import com.bernard.misael.quizz.model.QuizzForm;
|
||||||
|
import com.bernard.misael.auth.AuthController;
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
import com.bernard.misael.quizz.questions.QTypes;
|
||||||
|
import com.bernard.misael.quizz.repository.QuizzFormRepository;
|
||||||
|
import com.bernard.misael.quizz.repository.QuizzRepository;
|
||||||
|
import com.bernard.misael.quizz.service.QuizzManager;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
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.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/questions")
|
||||||
|
public class QuestionsController {
|
||||||
|
|
||||||
|
private final UserService us;
|
||||||
|
private final QuizzManager qm;
|
||||||
|
private final QuizzRepository qrepo;
|
||||||
|
private final QuizzFormRepository qfrepo;
|
||||||
|
|
||||||
|
public QuestionsController(UserService us, QuizzManager qm, QuizzRepository qrepo, QuizzFormRepository qfrepo) {
|
||||||
|
this.us = us;
|
||||||
|
this.qm = qm;
|
||||||
|
this.qrepo = qrepo;
|
||||||
|
this.qfrepo = qfrepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(APIException.class)
|
||||||
|
public Object apiException(HttpServletRequest request, APIException e) {
|
||||||
|
if(request.getMethod().equals(HttpMethod.GET.toString()))
|
||||||
|
return new ResponseEntity<>(e.getMessage(), e.getStatus());
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
@GetMapping("/quizz")
|
||||||
|
public String getQuizz(Model model, Principal p) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u!=null) {
|
||||||
|
model.addAttribute("answerableQuizz",qm.answerableQuizz(u));
|
||||||
|
model.addAttribute("editableQuizz",qm.editableQuizz(u));
|
||||||
|
model.addAttribute("completedQuizz",qm.completedQuizz(u));
|
||||||
|
}
|
||||||
|
return "quizz.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List all forms started or finished by the user
|
||||||
|
*/
|
||||||
|
@GetMapping("/forms")
|
||||||
|
public String getForms(Model model, Principal p) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u!=null) {
|
||||||
|
model.addAttribute("finishedForms",qfrepo.findByUserAndDoneTrue(u));
|
||||||
|
model.addAttribute("openForms",qfrepo.findByUserAndDoneFalse(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
return "forms.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Show one (completed) form of one user
|
||||||
|
*/
|
||||||
|
@GetMapping("/showform/{id}")
|
||||||
|
public Object showForm(@PathVariable("id") long id, Model m, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
QuizzForm qf = qm.getQuizzForm(u, id);
|
||||||
|
m.addAttribute("formId", qf.getId());
|
||||||
|
return "showform.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Watch advancements of all forms for a specific quizz id
|
||||||
|
*/
|
||||||
|
@GetMapping("/watch/{id}")
|
||||||
|
public Object showFormsAdvancements(@PathVariable("id") long id, Model m, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
Quizz q = qm.getQuizz4Watch(u, id);
|
||||||
|
m.addAttribute("quizzId", q.getId());
|
||||||
|
return "watchquizz.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form
|
||||||
|
*/
|
||||||
|
@PostMapping("/getformdata/{id}")
|
||||||
|
public Object showFormApi(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.getQuizzFormData(u, id);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the forms for a specific quizz
|
||||||
|
*/
|
||||||
|
@PostMapping("/getallformsdata/{id}")
|
||||||
|
public Object getAllFormsData(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.getAllFormsData(u, id);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form advancement for every form of a quizz
|
||||||
|
*/
|
||||||
|
@PostMapping("/watchdata/{id}")
|
||||||
|
public Object watchData(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.getQuizzFormAdvancments(u, id);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form
|
||||||
|
*/
|
||||||
|
@PostMapping("/duplicate-quizz/{id}")
|
||||||
|
public Object duplicateQuizz(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
Quizz q = qm.duplicateQuizz(u, id);
|
||||||
|
if(q == null)
|
||||||
|
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||||
|
return new ResponseEntity<>(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form
|
||||||
|
*/
|
||||||
|
@PostMapping("/mark-complete/{id}")
|
||||||
|
public Object markComplete(@PathVariable("id") long id, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
if(!qm.markComplete(u, id))
|
||||||
|
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||||
|
return new ResponseEntity<>(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* API set the last public question
|
||||||
|
*/
|
||||||
|
@PostMapping("/set-public-question-count/{id}")
|
||||||
|
public Object setPublicQuestionCount(@PathVariable("id") long id, @RequestBody JsonNode data, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode pqc = data.get("publicQuestionCount");
|
||||||
|
Integer pqcI = pqc.isNull() ? null : pqc.asInt();
|
||||||
|
if(!qm.setPublicQuestionCount(u, id, pqcI))
|
||||||
|
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||||
|
return new ResponseEntity<>(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/form/{q}")
|
||||||
|
public String formpage(@PathVariable("q") long quizzId, Principal p, Model m, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if (u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
m.addAttribute("formid", quizzId);
|
||||||
|
Quizz q = qrepo.getReferenceById(quizzId);
|
||||||
|
m.addAttribute("quizzLength",q.getQuestionCount());
|
||||||
|
|
||||||
|
return "form";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/question/{q}")
|
||||||
|
public Object question(@PathVariable("q") long quizzId, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.next(u, quizzId);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/answer/{q}")
|
||||||
|
public Object answer(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.answer(u, quizzId, data);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/new-quizz")
|
||||||
|
@Secured("CREATE_QUIZZ")
|
||||||
|
public Object newQuizz(Principal p, Model m, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if (u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
Quizz q = qm.newQuizz(u);
|
||||||
|
|
||||||
|
return "redirect:/questions/quizz-edit/"+Long.toString(q.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/quizz-edit/{q}")
|
||||||
|
public Object quizzEdit(@PathVariable("q") long quizzId, Principal p, Model m, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
Quizz q = qm.getQuizz4Edit(u, quizzId);
|
||||||
|
m.addAttribute("quizzId", q.getId());
|
||||||
|
return "quizz-edit";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/quizz-edit/{q}/get")
|
||||||
|
public Object quizzSetName(@PathVariable("q") long quizzId, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.getQuizzInfo(u, quizzId);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/quizz-edit/{q}/set-name")
|
||||||
|
public Object quizzSetName(@PathVariable("q") long quizzId, @RequestBody String data, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.setQuizzName(u, quizzId, data);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/quizz-edit/{q}/add-question")
|
||||||
|
public Object quizzAddQuestion(@PathVariable("q") long quizzId, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.addQuestion(u, quizzId);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/quizz-edit/{q}/remove-question/{qi}")
|
||||||
|
public Object quizzSetName(@PathVariable("q") long quizzId, @PathVariable("qi") long questionId, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
JsonNode out = qm.removeQuestion(u, quizzId, questionId);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/quizz-edit/{q}/reorder-questions")
|
||||||
|
public Object quizzReorderQuestions(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p, HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
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 Object quizzEditQuestion(@PathVariable("q") long quizzId,
|
||||||
|
@PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
|
||||||
|
JsonNode out = qm.editQuestion(u, quizzId, questionId, data);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/quizz-edit/{q}/set-question-type/{qi}")
|
||||||
|
public Object quizzSetQuestionType(@PathVariable("q") long quizzId,
|
||||||
|
@PathVariable("qi") long questionId, @RequestBody JsonNode data, Principal p,
|
||||||
|
HttpServletRequest request) {
|
||||||
|
User u = us.ofPrincipal(p);
|
||||||
|
if(u==null)
|
||||||
|
return AuthController.requiresLogin(request.getRequestURI());
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.bernard.misael.model;
|
package com.bernard.misael.quizz.model;
|
||||||
|
|
||||||
import com.bernard.misael.service.JsonNodeConverter;
|
import com.bernard.misael.quizz.service.JsonNodeConverter;
|
||||||
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;
|
||||||
|
|
||||||
+8
-15
@@ -1,10 +1,9 @@
|
|||||||
package com.bernard.misael.model;
|
package com.bernard.misael.quizz.model;
|
||||||
|
|
||||||
import com.bernard.misael.questions.QTypes;
|
import com.bernard.misael.quizz.questions.QTypes;
|
||||||
import com.bernard.misael.questions.QuestionType;
|
import com.bernard.misael.quizz.questions.QuestionType;
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.bernard.misael.quizz.service.JsonNodeConverter;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -36,19 +35,13 @@ public class Question {
|
|||||||
private Quizz quizz;
|
private Quizz quizz;
|
||||||
|
|
||||||
@Setter
|
@Setter
|
||||||
private String value;
|
@Convert(converter = JsonNodeConverter.class)
|
||||||
|
private JsonNode value;
|
||||||
|
|
||||||
transient QuestionType qtype = null;
|
transient QuestionType qtype = null;
|
||||||
public QuestionType getQT() {
|
public QuestionType getQT() {
|
||||||
if(qtype==null){
|
if(qtype==null){
|
||||||
ObjectMapper om = new ObjectMapper();
|
qtype = type.construct(value);
|
||||||
JsonNode jsNode;
|
|
||||||
try {
|
|
||||||
jsNode = om.readTree(value);
|
|
||||||
qtype = type.construct(jsNode);
|
|
||||||
} catch (JsonProcessingException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return qtype;
|
return qtype;
|
||||||
}
|
}
|
||||||
+7
-2
@@ -1,9 +1,11 @@
|
|||||||
package com.bernard.misael.model;
|
package com.bernard.misael.quizz.model;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.hibernate.annotations.ColumnDefault;
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -31,7 +33,7 @@ public class Quizz {
|
|||||||
|
|
||||||
@Column(nullable=false)
|
@Column(nullable=false)
|
||||||
@ColumnDefault("false")
|
@ColumnDefault("false")
|
||||||
private boolean isPublic;
|
private boolean isComplete;
|
||||||
|
|
||||||
@Column(nullable=false)
|
@Column(nullable=false)
|
||||||
@ColumnDefault("0")
|
@ColumnDefault("0")
|
||||||
@@ -39,4 +41,7 @@ public class Quizz {
|
|||||||
|
|
||||||
@OneToMany(mappedBy="quizz")
|
@OneToMany(mappedBy="quizz")
|
||||||
private Set<Question> questions;
|
private Set<Question> questions;
|
||||||
|
|
||||||
|
@Column(nullable=true)
|
||||||
|
private Integer publicQuestionCount;
|
||||||
}
|
}
|
||||||
+3
-1
@@ -1,4 +1,6 @@
|
|||||||
package com.bernard.misael.model;
|
package com.bernard.misael.quizz.model;
|
||||||
|
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
import jakarta.persistence.Entity;
|
||||||
import jakarta.persistence.GeneratedValue;
|
import jakarta.persistence.GeneratedValue;
|
||||||
+4
-4
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.questions;
|
package com.bernard.misael.quizz.questions;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -13,9 +13,9 @@ import java.util.stream.StreamSupport;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.bernard.misael.service.exception.MalformedAnswerException;
|
import com.bernard.misael.quizz.service.exception.MalformedAnswerException;
|
||||||
import com.bernard.misael.service.exception.MalformedClientAnswerException;
|
import com.bernard.misael.quizz.service.exception.MalformedClientAnswerException;
|
||||||
import com.bernard.misael.service.exception.QuestionTypeException;
|
import com.bernard.misael.quizz.service.exception.QuestionTypeException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.questions;
|
package com.bernard.misael.quizz. questions;
|
||||||
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
package com.bernard.misael.questions;
|
package com.bernard.misael.quizz. questions;
|
||||||
|
|
||||||
import com.bernard.misael.service.exception.QuestionTypeException;
|
import com.bernard.misael.quizz.service.exception.QuestionTypeException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.bernard.misael.quizz.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import com.bernard.misael.quizz.model.Answer;
|
||||||
|
import com.bernard.misael.quizz.model.Question;
|
||||||
|
import com.bernard.misael.quizz.model.QuizzForm;
|
||||||
|
|
||||||
|
public interface AnswerRepository extends JpaRepository<Answer,Long> {
|
||||||
|
|
||||||
|
public Answer findByFormAndQuestion(QuizzForm qf, Question q);
|
||||||
|
public List<Answer> findByFormAndQuestionIn(QuizzForm qf, List<Question> qz);
|
||||||
|
|
||||||
|
}
|
||||||
+5
-3
@@ -1,16 +1,18 @@
|
|||||||
package com.bernard.misael.repository;
|
package com.bernard.misael.quizz.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
import com.bernard.misael.model.Question;
|
import com.bernard.misael.quizz.model.Question;
|
||||||
import com.bernard.misael.model.Quizz;
|
import com.bernard.misael.quizz.model.Quizz;
|
||||||
|
|
||||||
|
|
||||||
public interface QuestionRepository extends JpaRepository<Question,Long> {
|
public interface QuestionRepository extends JpaRepository<Question,Long> {
|
||||||
|
|
||||||
public Question findByQuizzAndIndex(Quizz quizz, int index);
|
public Question findByQuizzAndIndex(Quizz quizz, int index);
|
||||||
public Set<Question> findByQuizz(Quizz quizz);
|
public Set<Question> findByQuizz(Quizz quizz);
|
||||||
|
public List<Question> findByQuizzOrderByIndexAsc(Quizz quizz);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.bernard.misael.quizz.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import com.bernard.misael.quizz.model.Quizz;
|
||||||
|
import com.bernard.misael.quizz.model.QuizzForm;
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
|
||||||
|
public interface QuizzFormRepository extends JpaRepository<QuizzForm,Long> {
|
||||||
|
|
||||||
|
public List<QuizzForm> findByQuizz(Quizz q);
|
||||||
|
|
||||||
|
public QuizzForm findByUserAndQuizz(User u, Quizz q);
|
||||||
|
public List<QuizzForm> findByUserAndDoneTrue(User u);
|
||||||
|
public List<QuizzForm> findByUserAndDoneFalse(User u);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bernard.misael.quizz.repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
|
||||||
|
import com.bernard.misael.quizz.model.Quizz;
|
||||||
|
import com.bernard.misael.auth.model.User;
|
||||||
|
|
||||||
|
public interface QuizzRepository extends JpaRepository<Quizz,Long> {
|
||||||
|
|
||||||
|
public @NonNull Optional<Quizz> findById(@NonNull Long id);
|
||||||
|
|
||||||
|
public @NonNull Set<Quizz> findByPublicQuestionCountIsNotNullAndIsCompleteTrue();
|
||||||
|
public @NonNull Set<Quizz> findByOwnerAndIsCompleteTrue(@NonNull User owner);
|
||||||
|
public @NonNull Set<Quizz> findByOwnerAndIsCompleteFalse(@NonNull User owner);
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.service;
|
package com.bernard.misael.quizz.service;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.bernard.misael.quizz.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.QuizzForm;
|
||||||
|
import com.bernard.misael.quizz.questions.QTypes;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
public interface QuizzManager {
|
||||||
|
|
||||||
|
public JsonNode answer(User user, long quizzId, JsonNode data);
|
||||||
|
public JsonNode next(User user, long quizzId);
|
||||||
|
|
||||||
|
public Quizz newQuizz(User user);
|
||||||
|
|
||||||
|
public List<Quizz> editableQuizz(User user);
|
||||||
|
public List<Quizz> completedQuizz(User user);
|
||||||
|
public List<Quizz> answerableQuizz(User user);
|
||||||
|
|
||||||
|
public Quizz getQuizz4Edit(User user, long quizzId) throws APIException;
|
||||||
|
public QuizzForm getQuizzForm(User user, long quizzFormId) throws APIException;
|
||||||
|
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 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);
|
||||||
|
|
||||||
|
public JsonNode getQuizzFormData(User user, long quizzFormId);
|
||||||
|
public JsonNode getAllFormsData(User u, long id);
|
||||||
|
public JsonNode getQuizzFormAdvancments(User user, long quizzId);
|
||||||
|
|
||||||
|
public Quizz duplicateQuizz(User user, long quizzId);
|
||||||
|
public boolean markComplete(User user, long quizzId);
|
||||||
|
public boolean setPublicQuestionCount(User u, long quizzId, Integer publicQuestionCount);
|
||||||
|
}
|
||||||
@@ -0,0 +1,626 @@
|
|||||||
|
package com.bernard.misael.quizz.service;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
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.Quizz;
|
||||||
|
import com.bernard.misael.quizz.model.QuizzForm;
|
||||||
|
import com.bernard.misael.quizz.questions.QTypes;
|
||||||
|
import com.bernard.misael.quizz.questions.QuestionType.AnswerResult;
|
||||||
|
import com.bernard.misael.quizz.repository.AnswerRepository;
|
||||||
|
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.MalformedClientAnswerException;
|
||||||
|
import com.bernard.misael.quizz.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;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class QuizzManagerImpl implements QuizzManager {
|
||||||
|
|
||||||
|
private final QuizzFormRepository qfRepository;
|
||||||
|
private final QuizzRepository qRepository;
|
||||||
|
private final QuestionRepository questionRepository;
|
||||||
|
private final AnswerRepository answerRepository;
|
||||||
|
private final 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
|
||||||
|
public JsonNode answer(User user, long quizzId, JsonNode data) {
|
||||||
|
Quizz quizz = getQuizz4Answer(user, quizzId);
|
||||||
|
if(!data.has("index") || !data.get("index").isInt())
|
||||||
|
throw new APIException("Request should contain the question index");
|
||||||
|
if(!data.has("step") || !data.get("step").isInt())
|
||||||
|
throw new APIException("Request should contain the answer step");
|
||||||
|
if(!data.has("data"))
|
||||||
|
throw new APIException("Request should contain the answer data");
|
||||||
|
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
|
||||||
|
if(qf == null)
|
||||||
|
throw new APIException("The quizzform does not exist, ask the question first", HttpStatus.NOT_FOUND);
|
||||||
|
if(qf.isDone())
|
||||||
|
throw new APIException("You're done with the quizz, you cannot answer anymore", HttpStatus.CONFLICT);
|
||||||
|
int qindex = qf.getCurrentQuestion();
|
||||||
|
if(qindex != data.get("index").intValue())
|
||||||
|
throw new APIException(
|
||||||
|
"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))
|
||||||
|
throw new APIException("La question suivante est encore bloquée", HttpStatus.CONFLICT);
|
||||||
|
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
|
||||||
|
if(q == null)
|
||||||
|
throw new APIException("Could not find question %d".formatted(qindex), HttpStatus.NOT_FOUND);
|
||||||
|
int step = qf.getAnswerStep();
|
||||||
|
if(step != data.get("step").intValue())
|
||||||
|
throw new APIException(
|
||||||
|
"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);
|
||||||
|
if(answer == null)
|
||||||
|
throw new APIException("The database answer object does not exist, ask the question first", HttpStatus.CONFLICT);
|
||||||
|
|
||||||
|
JsonNode answerData = answer.getValue();
|
||||||
|
AnswerResult result;
|
||||||
|
try {
|
||||||
|
result = q.getQT().clientAnswers(step, answerData, data.get("data"));
|
||||||
|
} catch (MalformedAnswerException e) {
|
||||||
|
throw new APIException("The previous answer stored in database is invalid", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
} catch (MalformedClientAnswerException e) {
|
||||||
|
throw new APIException("This answer is not valid here");
|
||||||
|
} catch (QuestionTypeException e) {
|
||||||
|
throw new APIException("Unknown error from the QuestionType", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new APIException("The QuestionType did not recognize the step of the question");
|
||||||
|
}
|
||||||
|
if(result.isNextQuestion()) {
|
||||||
|
qf.setCurrentQuestion(qindex+1);
|
||||||
|
qf.setAnswerStep(0);
|
||||||
|
if(qf.getCurrentQuestion() >= quizz.getQuestionCount()){
|
||||||
|
// The quizz is done
|
||||||
|
qf.setDone(true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qf.setAnswerStep(result.getNextStep());
|
||||||
|
}
|
||||||
|
answer.setValue(result.getNewAnswer());
|
||||||
|
answerRepository.save(answer);
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode next(User user, long quizzId) {
|
||||||
|
Quizz quizz = getQuizz4Answer(user, quizzId);
|
||||||
|
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
|
||||||
|
if(qf == null){
|
||||||
|
// We should create the quizzform
|
||||||
|
qf = newQuizzForm(user, quizz);
|
||||||
|
}
|
||||||
|
if(qf.isDone())
|
||||||
|
throw new APIException("No more questions", HttpStatus.OK);
|
||||||
|
int qindex = qf.getCurrentQuestion();
|
||||||
|
if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE))
|
||||||
|
throw new APIException("La question suivante est encore bloquée", HttpStatus.OK);
|
||||||
|
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
|
||||||
|
if(q == null)
|
||||||
|
throw new APIException("Could not find question %d".formatted(qindex), HttpStatus.NOT_FOUND);
|
||||||
|
int step = qf.getAnswerStep();
|
||||||
|
Answer answer = answerRepository.findByFormAndQuestion(qf, q);
|
||||||
|
if(answer==null){
|
||||||
|
// We construct the blank answer
|
||||||
|
answer = new Answer(qf,q);
|
||||||
|
answerRepository.save(answer);
|
||||||
|
}
|
||||||
|
|
||||||
|
JsonNode answerData;
|
||||||
|
answerData = answer.getValue();
|
||||||
|
JsonNode qdata;
|
||||||
|
try {
|
||||||
|
qdata = q.getQT().clientQuestionData(step, answerData);
|
||||||
|
} catch (MalformedAnswerException e) {
|
||||||
|
throw new APIException("The previous answer stored in database is invalid", HttpStatus.CONFLICT);
|
||||||
|
} catch (QuestionTypeException e) {
|
||||||
|
throw new APIException("Unknown error from the QuestionType", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new APIException("The QuestionType did not recognize the step of the question");
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
out.set("index", JsonNodeFactory.instance.numberNode(qindex));
|
||||||
|
out.set("step", JsonNodeFactory.instance.numberNode(step));
|
||||||
|
out.set("data", qdata);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private QuizzForm newQuizzForm(@NonNull User user, Quizz quizz) {
|
||||||
|
QuizzForm qf = new QuizzForm();
|
||||||
|
qf.setUser(user);
|
||||||
|
qf.setQuizz(quizz);
|
||||||
|
qf.setDone(false);
|
||||||
|
qf.setCurrentQuestion(0);
|
||||||
|
qf.setAnswerStep(0);
|
||||||
|
qfRepository.save(qf);
|
||||||
|
return qf;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Random rand = new Random();
|
||||||
|
@Override
|
||||||
|
public Quizz newQuizz(User user) {
|
||||||
|
Quizz q = new Quizz();
|
||||||
|
q.setName("Super questions de %s (%X)".formatted(user.getName(),rand.nextInt(0xFFFFFFF)));
|
||||||
|
q.setOwner(user);
|
||||||
|
q = qRepository.save(q);
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Quizz> answerableQuizz(User user) {
|
||||||
|
Set<Quizz> ownQuizz = qRepository.findByOwnerAndIsCompleteTrue(user);
|
||||||
|
Set<Quizz> publicQuizz = qRepository.findByPublicQuestionCountIsNotNullAndIsCompleteTrue();
|
||||||
|
publicQuizz.removeAll(ownQuizz);
|
||||||
|
|
||||||
|
return Stream.concat(
|
||||||
|
ownQuizz.stream()
|
||||||
|
.sorted((q1,q2) -> q1.getName().compareTo(q2.getName())),
|
||||||
|
publicQuizz.stream()
|
||||||
|
.sorted((q1,q2) -> q1.getName().compareTo(q2.getName()))
|
||||||
|
).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Quizz> editableQuizz(User user) {
|
||||||
|
Set<Quizz> ownQuizz = qRepository.findByOwnerAndIsCompleteFalse(user);
|
||||||
|
return ownQuizz.stream()
|
||||||
|
.sorted((q1,q2) -> q1.getName().compareTo(q2.getName()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Quizz> completedQuizz(User user) {
|
||||||
|
Set<Quizz> ownQuizz = qRepository.findByOwnerAndIsCompleteTrue(user);
|
||||||
|
return ownQuizz.stream()
|
||||||
|
.sorted((q1,q2) -> q1.getName().compareTo(q2.getName()))
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks that the given user can edit 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 getQuizz4Edit(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))
|
||||||
|
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
|
||||||
|
public JsonNode getQuizzInfo(User user, long quizzId) throws APIException{
|
||||||
|
if(user == null)
|
||||||
|
throw LOGIN_REQUIRED_EXCEPTION;
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
|
||||||
|
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()));
|
||||||
|
nn.set("value",q.getValue());
|
||||||
|
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) {
|
||||||
|
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
|
||||||
|
if(newName.isBlank() || newName.length()>255)
|
||||||
|
throw new APIException("Le nom est invalide");
|
||||||
|
|
||||||
|
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) throws APIException {
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
|
||||||
|
Question q = new Question();
|
||||||
|
q.setType(DEFAULT_QTYPE);
|
||||||
|
JsonNode n = DEFAULT_QTYPE.getDefaultQuestion();
|
||||||
|
q.setValue(n);
|
||||||
|
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("id", JsonNodeFactory.instance.numberNode(q.getId()));
|
||||||
|
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) throws APIException {
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
Question q = getQuestion(quizz, 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) throws APIException {
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
|
||||||
|
// We need that the base set of neworder is the list of ids
|
||||||
|
// 1) The length is right
|
||||||
|
if(quizz.getQuestionCount() != newOrder.size())
|
||||||
|
throw new APIException("You must put every question in order");
|
||||||
|
// 2) There is no duplicates
|
||||||
|
if(new HashSet<Long>(newOrder).size() != newOrder.size())
|
||||||
|
throw new APIException("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++){
|
||||||
|
Question q = getQuestion(quizz, newOrder.get(i));
|
||||||
|
questions.add(q);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
Question q = getQuestion(quizz, questionId);
|
||||||
|
|
||||||
|
if(!q.getType().validate(value))
|
||||||
|
throw new APIException("Invalid question value");
|
||||||
|
q.setValue(value);
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Quizz quizz = getQuizz4Edit(user, quizzId);
|
||||||
|
Question q = getQuestion(quizz, questionId);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
} else {
|
||||||
|
n =q.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
out.set("new", n);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode getQuizzFormData(User user, long quizzFormId) {
|
||||||
|
QuizzForm form = getQuizzForm(user, quizzFormId);
|
||||||
|
List<Question> questions = questionRepository.findByQuizzOrderByIndexAsc(form.getQuizz());
|
||||||
|
List<Answer> answers = answerRepository.findByFormAndQuestionIn(form, questions);
|
||||||
|
assert questions.size() == answers.size();
|
||||||
|
ArrayNode answersNode = JsonNodeFactory.instance.arrayNode();
|
||||||
|
for(int i=0;i<questions.size();i++) {
|
||||||
|
ObjectNode anode = JsonNodeFactory.instance.objectNode();
|
||||||
|
anode.set("qid", JsonNodeFactory.instance.numberNode(questions.get(i).getId()));
|
||||||
|
anode.set("type", JsonNodeFactory.instance.textNode(questions.get(i).getType().name()));
|
||||||
|
anode.set("qvalue", questions.get(i).getValue());
|
||||||
|
anode.set("aid", JsonNodeFactory.instance.numberNode(answers.get(i).getId()));
|
||||||
|
anode.set("avalue", answers.get(i).getValue());
|
||||||
|
answersNode.add(anode);
|
||||||
|
}
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
out.set("data", answersNode);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode getAllFormsData(User u, long quizzId) {
|
||||||
|
Quizz quizz = getQuizz4Watch(u, quizzId);
|
||||||
|
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("id",JsonNodeFactory.instance.numberNode(quizz.getId()));
|
||||||
|
out.set("name",JsonNodeFactory.instance.textNode(quizz.getName()));
|
||||||
|
|
||||||
|
ArrayNode questionNode = JsonNodeFactory.instance.arrayNode(quizz.getQuestionCount());
|
||||||
|
List<Question> questions = quizz.getQuestions().stream().sorted((p,q) -> Integer.compare(p.getIndex(),q.getIndex())).toList();
|
||||||
|
for(int i=0;i<questions.size();i++) {
|
||||||
|
ObjectNode qNode = JsonNodeFactory.instance.objectNode();
|
||||||
|
qNode.set("id", JsonNodeFactory.instance.numberNode(questions.get(i).getId()));
|
||||||
|
qNode.set("type", JsonNodeFactory.instance.textNode(questions.get(i).getType().name()));
|
||||||
|
qNode.set("value",questions.get(i).getValue());
|
||||||
|
questionNode.add(qNode);
|
||||||
|
}
|
||||||
|
out.set("questions", questionNode);
|
||||||
|
|
||||||
|
ObjectNode qfzNodes = JsonNodeFactory.instance.objectNode();
|
||||||
|
for(QuizzForm qf : qfRepository.findByQuizz(quizz)) {
|
||||||
|
ObjectNode qfNode = JsonNodeFactory.instance.objectNode();
|
||||||
|
qfNode.set("id", JsonNodeFactory.instance.numberNode(qf.getId()));
|
||||||
|
qfNode.set("currentQuestion", JsonNodeFactory.instance.numberNode(qf.getCurrentQuestion()));
|
||||||
|
qfNode.set("answerStep", JsonNodeFactory.instance.numberNode(qf.getAnswerStep()));
|
||||||
|
qfNode.set("done", JsonNodeFactory.instance.booleanNode(qf.isDone()));
|
||||||
|
|
||||||
|
List<Answer> answers = answerRepository.findByFormAndQuestionIn(qf, questions);
|
||||||
|
answers.sort((a1,a2) -> Integer.compare(a1.getQuestion().getIndex(),a2.getQuestion().getIndex()));
|
||||||
|
ArrayNode answersNode = JsonNodeFactory.instance.arrayNode();
|
||||||
|
for(int i=0;i<answers.size();i++) {
|
||||||
|
answersNode.add(answers.get(i).getValue());
|
||||||
|
}
|
||||||
|
qfNode.set("answers", answersNode);
|
||||||
|
qfzNodes.set(qf.getUser().getName(), qfNode);
|
||||||
|
}
|
||||||
|
out.set("forms", qfzNodes);
|
||||||
|
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode getQuizzFormAdvancments(User user, long quizzId) {
|
||||||
|
Quizz quizz = getQuizz4Watch(user, quizzId);
|
||||||
|
List<QuizzForm> quizzForms = qfRepository.findByQuizz(quizz);
|
||||||
|
quizzForms.sort((qfa,qfb) -> qfa.getUser().getName().compareTo(qfb.getUser().getName()));
|
||||||
|
|
||||||
|
ArrayNode formsNode = JsonNodeFactory.instance.arrayNode();
|
||||||
|
for(QuizzForm qf : quizzForms) {
|
||||||
|
ObjectNode anode = JsonNodeFactory.instance.objectNode();
|
||||||
|
anode.set("qfid", JsonNodeFactory.instance.numberNode(qf.getId()));
|
||||||
|
anode.set("done", JsonNodeFactory.instance.booleanNode(qf.isDone()));
|
||||||
|
anode.set("position", JsonNodeFactory.instance.numberNode(qf.getCurrentQuestion()));
|
||||||
|
anode.set("step", JsonNodeFactory.instance.numberNode(qf.getAnswerStep()));
|
||||||
|
anode.set("username", JsonNodeFactory.instance.textNode(qf.getUser().getName()));
|
||||||
|
formsNode.add(anode);
|
||||||
|
}
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
out.set("publicQuestionCount", quizz.getPublicQuestionCount()==null?
|
||||||
|
JsonNodeFactory.instance.nullNode():
|
||||||
|
JsonNodeFactory.instance.numberNode(quizz.getPublicQuestionCount()));
|
||||||
|
out.set("questionCount", JsonNodeFactory.instance.numberNode(quizz.getQuestionCount()));
|
||||||
|
out.set("data", formsNode);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Quizz duplicateQuizz(User u, long quizzId) {
|
||||||
|
Quizz q = getQuizz4Edit(u, quizzId);
|
||||||
|
|
||||||
|
Quizz nq = new Quizz();
|
||||||
|
nq.setName(q.getName() + "("+ Integer.toHexString(rand.nextInt(0xFFFFFFF)) +")");
|
||||||
|
nq.setOwner(q.getOwner());
|
||||||
|
nq.setQuestionCount(q.getQuestionCount());
|
||||||
|
nq.setPublicQuestionCount(q.getPublicQuestionCount());
|
||||||
|
nq.setComplete(q.isComplete());
|
||||||
|
|
||||||
|
nq = qRepository.save(nq);
|
||||||
|
|
||||||
|
// We duplicate questions
|
||||||
|
for (Question qu : q.getQuestions()) {
|
||||||
|
Question nqu = new Question();
|
||||||
|
nqu.setIndex(qu.getIndex());
|
||||||
|
nqu.setType(qu.getType());
|
||||||
|
nqu.setValue(qu.getValue());
|
||||||
|
nqu.setQuizz(nq);
|
||||||
|
questionRepository.save(nqu);
|
||||||
|
}
|
||||||
|
return nq;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean markComplete(User u, long quizzId) {
|
||||||
|
Quizz q = getQuizz4Edit(u, quizzId);
|
||||||
|
|
||||||
|
q.setPublicQuestionCount(null);
|
||||||
|
q.setComplete(true);
|
||||||
|
qRepository.save(q);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setPublicQuestionCount(User u, long quizzId, Integer publicQuestionCount) {
|
||||||
|
Quizz q = getQuizz4Watch(u, quizzId);
|
||||||
|
if(!q.isComplete())
|
||||||
|
return false;
|
||||||
|
if(publicQuestionCount != null && (publicQuestionCount > q.getQuestionCount() || publicQuestionCount < 0))
|
||||||
|
return false;
|
||||||
|
q.setPublicQuestionCount(publicQuestionCount);
|
||||||
|
qRepository.save(q);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.service.exception;
|
package com.bernard.misael.quizz.service.exception;
|
||||||
|
|
||||||
public class MalformedAnswerException extends QuestionTypeException{
|
public class MalformedAnswerException extends QuestionTypeException{
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.service.exception;
|
package com.bernard.misael.quizz.service.exception;
|
||||||
|
|
||||||
public class MalformedClientAnswerException extends QuestionTypeException {
|
public class MalformedClientAnswerException extends QuestionTypeException {
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.bernard.misael.service.exception;
|
package com.bernard.misael.quizz.service.exception;
|
||||||
|
|
||||||
public abstract class QuestionTypeException extends Exception{
|
public abstract class QuestionTypeException extends Exception{
|
||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.bernard.misael.repository;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.Answer;
|
|
||||||
import com.bernard.misael.model.Question;
|
|
||||||
import com.bernard.misael.model.QuizzForm;
|
|
||||||
|
|
||||||
public interface AnswerRepository extends JpaRepository<Answer,Long> {
|
|
||||||
|
|
||||||
public Answer findByFormAndQuestion(QuizzForm qf, Question q);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.bernard.misael.repository;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.Quizz;
|
|
||||||
import com.bernard.misael.model.QuizzForm;
|
|
||||||
import com.bernard.misael.model.User;
|
|
||||||
|
|
||||||
public interface QuizzFormRepository extends JpaRepository<QuizzForm,Long> {
|
|
||||||
|
|
||||||
public QuizzForm findByUserAndQuizz(User u, Quizz q);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.bernard.misael.repository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.Quizz;
|
|
||||||
|
|
||||||
public interface QuizzRepository extends JpaRepository<Quizz,Long> {
|
|
||||||
|
|
||||||
public @NonNull Optional<Quizz> findById(@NonNull Long id);
|
|
||||||
|
|
||||||
public @NonNull Set<Quizz> findByIsPublicTrue();
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.bernard.misael.repository;
|
|
||||||
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.Role;
|
|
||||||
|
|
||||||
public interface RoleRepository extends JpaRepository<Role, Long> {
|
|
||||||
|
|
||||||
Role findByName(String name);
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
package com.bernard.misael.service;
|
|
||||||
|
|
||||||
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 {
|
|
||||||
|
|
||||||
public JsonNode answer(User user, long quizzId,JsonNode data);
|
|
||||||
public JsonNode next(User user, long quizzId);
|
|
||||||
|
|
||||||
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,433 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.Answer;
|
|
||||||
import com.bernard.misael.model.Question;
|
|
||||||
import com.bernard.misael.model.Quizz;
|
|
||||||
import com.bernard.misael.model.QuizzForm;
|
|
||||||
import com.bernard.misael.model.User;
|
|
||||||
import com.bernard.misael.questions.QTypes;
|
|
||||||
import com.bernard.misael.questions.QuestionType.AnswerResult;
|
|
||||||
import com.bernard.misael.repository.AnswerRepository;
|
|
||||||
import com.bernard.misael.repository.QuestionRepository;
|
|
||||||
import com.bernard.misael.repository.QuizzFormRepository;
|
|
||||||
import com.bernard.misael.repository.QuizzRepository;
|
|
||||||
import com.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;
|
|
||||||
|
|
||||||
import jakarta.persistence.EntityNotFoundException;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class QuizzManagerImpl implements QuizzManager {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
QuizzFormRepository qfRepository;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
QuizzRepository qRepository;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
QuestionRepository questionRepository;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
AnswerRepository answerRepository;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonNode answer(User user, long quizzId, JsonNode data) {
|
|
||||||
if(!data.has("index") || !data.get("index").isInt())
|
|
||||||
return errorNode("Request should contain the question index");
|
|
||||||
if(!data.has("step") || !data.get("step").isInt())
|
|
||||||
return errorNode("Request should contain the answer step");
|
|
||||||
if(!data.has("data"))
|
|
||||||
return errorNode("Request should contain the answer data");
|
|
||||||
if(user == null)
|
|
||||||
return errorNode("You must be logged in to answer");
|
|
||||||
Optional<Quizz> oquizz = qRepository.findById(quizzId);
|
|
||||||
if(!oquizz.isPresent())
|
|
||||||
return errorNode("Could not find the quizz with id "+quizzId);
|
|
||||||
Quizz quizz = oquizz.get();
|
|
||||||
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
|
|
||||||
if(qf == null)
|
|
||||||
return errorNode("The quizzform does not exist, ask the question first");
|
|
||||||
if(qf.isDone())
|
|
||||||
return errorNode("You're done with the quizz, you cannot answer anymore");
|
|
||||||
int qindex = qf.getCurrentQuestion();
|
|
||||||
if(qindex != data.get("index").intValue())
|
|
||||||
return errorNode("You are not answering the right question (you answer question "+data.get("index").intValue()
|
|
||||||
+" where you should answer question "+qindex+")");
|
|
||||||
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
|
|
||||||
if(q == null)
|
|
||||||
return errorNode("Could not find question "+qindex);
|
|
||||||
int step = qf.getAnswerStep();
|
|
||||||
if(step != data.get("step").intValue())
|
|
||||||
return errorNode("You are not answering the right step of the question (you answer step "+data.get("step").intValue()
|
|
||||||
+" where you should step question "+step+")");
|
|
||||||
|
|
||||||
Answer answer = answerRepository.findByFormAndQuestion(qf, q);
|
|
||||||
if(answer == null)
|
|
||||||
return errorNode("The database answer object does not exist, ask the question first");
|
|
||||||
|
|
||||||
JsonNode answerData = answer.getValue();
|
|
||||||
AnswerResult result;
|
|
||||||
try {
|
|
||||||
result = q.getQT().clientAnswers(step, answerData, data.get("data"));
|
|
||||||
} catch (MalformedAnswerException e) {
|
|
||||||
return errorNode("The previous answer stored in database is invalid");
|
|
||||||
} catch (MalformedClientAnswerException e) {
|
|
||||||
return errorNode("This answer is not valid here");
|
|
||||||
} catch (QuestionTypeException e) {
|
|
||||||
return errorNode("Unknown error from the QuestionType");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return errorNode("The QuestionType did not recognize the step of the question");
|
|
||||||
}
|
|
||||||
if(result.isNextQuestion()) {
|
|
||||||
qf.setCurrentQuestion(qindex+1);
|
|
||||||
qf.setAnswerStep(0);
|
|
||||||
if(qf.getCurrentQuestion() >= quizz.getQuestionCount()){
|
|
||||||
// The quizz is done
|
|
||||||
qf.setDone(true);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qf.setAnswerStep(result.getNextStep());
|
|
||||||
}
|
|
||||||
answer.setValue(result.getNewAnswer());
|
|
||||||
answerRepository.save(answer);
|
|
||||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
|
||||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonNode next(User user, long quizzId) {
|
|
||||||
if(user == null)
|
|
||||||
return errorNode("You need to be logged in to discover the questions");
|
|
||||||
Optional<Quizz> oquizz = qRepository.findById(quizzId);
|
|
||||||
if(!oquizz.isPresent())
|
|
||||||
return errorNode("Could not find quizz with id "+quizzId);
|
|
||||||
Quizz quizz = oquizz.get();
|
|
||||||
QuizzForm qf = qfRepository.findByUserAndQuizz(user, quizz);
|
|
||||||
if(qf == null){
|
|
||||||
// We should create the quizzform
|
|
||||||
qf = newQuizzForm(user, quizz);
|
|
||||||
}
|
|
||||||
if(qf.isDone())
|
|
||||||
return errorNode("No more questions");
|
|
||||||
int qindex = qf.getCurrentQuestion();
|
|
||||||
Question q = questionRepository.findByQuizzAndIndex(quizz,qindex);
|
|
||||||
if(q == null)
|
|
||||||
return errorNode("Could not find question "+qindex);
|
|
||||||
int step = qf.getAnswerStep();
|
|
||||||
Answer answer = answerRepository.findByFormAndQuestion(qf, q);
|
|
||||||
if(answer==null){
|
|
||||||
// We construct the blank answer
|
|
||||||
answer = new Answer(qf,q);
|
|
||||||
answerRepository.save(answer);
|
|
||||||
}
|
|
||||||
|
|
||||||
JsonNode answerData;
|
|
||||||
answerData = answer.getValue();
|
|
||||||
JsonNode qdata;
|
|
||||||
try {
|
|
||||||
qdata = q.getQT().clientQuestionData(step, answerData);
|
|
||||||
} catch (MalformedAnswerException e) {
|
|
||||||
return errorNode("The previous answer stored in database is invalid");
|
|
||||||
} catch (QuestionTypeException e) {
|
|
||||||
return errorNode("Unknown error from the QuestionType");
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return errorNode("The QuestionType did not recognize the step of the question");
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
|
||||||
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
|
||||||
out.set("index", JsonNodeFactory.instance.numberNode(qindex));
|
|
||||||
out.set("step", JsonNodeFactory.instance.numberNode(step));
|
|
||||||
out.set("data", qdata);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
public QuizzForm newQuizzForm(User user, Quizz quizz) {
|
|
||||||
QuizzForm qf = new QuizzForm();
|
|
||||||
qf.setUser(user);
|
|
||||||
qf.setQuizz(quizz);
|
|
||||||
qf.setDone(false);
|
|
||||||
qf.setCurrentQuestion(0);
|
|
||||||
qf.setAnswerStep(0);
|
|
||||||
qfRepository.save(qf);
|
|
||||||
return qf;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final JsonNode errorNode(String err){
|
|
||||||
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
|
||||||
out.set("success", JsonNodeFactory.instance.booleanNode(false));
|
|
||||||
out.set("message", JsonNodeFactory.instance.textNode(err));
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean canAccessQuizz(User user, long quizzId) {
|
|
||||||
try{
|
|
||||||
Quizz quizz = qRepository.getReferenceById(quizzId);
|
|
||||||
return quizz.isPublic() || quizz.getOwner().equals(user);
|
|
||||||
} catch (EntityNotFoundException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Quizz> accessibleQuizz(User user) {
|
|
||||||
Set<Quizz> ownQuizz = user.getMyQuizzs();
|
|
||||||
Set<Quizz> publicQuizz = qRepository.findByIsPublicTrue();
|
|
||||||
publicQuizz.removeAll(ownQuizz);
|
|
||||||
|
|
||||||
return Stream.concat(
|
|
||||||
ownQuizz.stream()
|
|
||||||
.sorted((q1,q2) -> q1.getName().compareTo(q2.getName())),
|
|
||||||
publicQuizz.stream()
|
|
||||||
.sorted((q1,q2) -> q1.getName().compareTo(q2.getName()))
|
|
||||||
).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,16 +0,0 @@
|
|||||||
package com.bernard.misael.service;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.User;
|
|
||||||
import com.bernard.misael.service.dto.UserDto;
|
|
||||||
|
|
||||||
public interface UserService {
|
|
||||||
|
|
||||||
void saveUser(UserDto userDto);
|
|
||||||
void changePassword(User user, String password);
|
|
||||||
|
|
||||||
User findUserByName(String name);
|
|
||||||
|
|
||||||
List<UserDto> findAllUsers();
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package com.bernard.misael.service;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
import com.bernard.misael.model.User;
|
|
||||||
import com.bernard.misael.repository.UserRepository;
|
|
||||||
import com.bernard.misael.service.dto.UserDto;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class UserServiceImpl implements UserService {
|
|
||||||
|
|
||||||
private UserRepository userRepository;
|
|
||||||
private PasswordEncoder passwordEncoder;
|
|
||||||
|
|
||||||
public UserServiceImpl(UserRepository userRepository,
|
|
||||||
PasswordEncoder passwordEncoder) {
|
|
||||||
this.userRepository = userRepository;
|
|
||||||
this.passwordEncoder = passwordEncoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void saveUser(UserDto userDto) {
|
|
||||||
User user = new User();
|
|
||||||
user.setName(userDto.getName());
|
|
||||||
// encrypt the password using spring security
|
|
||||||
user.setPassword(passwordEncoder.encode(userDto.getPassword()));
|
|
||||||
|
|
||||||
user.setRoles(List.of());
|
|
||||||
userRepository.save(user);
|
|
||||||
}
|
|
||||||
@Override
|
|
||||||
public void changePassword(User user, String password) {
|
|
||||||
user.setPassword(passwordEncoder.encode(password));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public User findUserByName(String name) {
|
|
||||||
return userRepository.findByName(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<UserDto> findAllUsers() {
|
|
||||||
List<User> users = userRepository.findAll();
|
|
||||||
return users.stream()
|
|
||||||
.map((user) -> mapToUserDto(user))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserDto mapToUserDto(User user){
|
|
||||||
UserDto userDto = new UserDto();
|
|
||||||
userDto.setName(user.getName());
|
|
||||||
return userDto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.bernard.misael.thecrew;
|
||||||
|
|
||||||
|
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.WebMvcConfigurer;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class TheCrewConfig implements WebMvcConfigurer {
|
||||||
|
@Override
|
||||||
|
public void addResourceHandlers(@NonNull ResourceHandlerRegistry registry) {
|
||||||
|
|
||||||
|
String imgFolder = System.getenv("THECREW_IMAGES_FOLDER");
|
||||||
|
registry.addResourceHandler("/thecrew/**")
|
||||||
|
.addResourceLocations("file://"+imgFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.bernard.misael.thecrew;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.util.FileCopyUtils;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/thecrew")
|
||||||
|
public class TheCrewController {
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String getTheCrewPage() {
|
||||||
|
return "thecrew.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/missions.json")
|
||||||
|
public ResponseEntity<String> getMissions() {
|
||||||
|
File missionsFile = new File(System.getenv("THECREW_MISSIONS_FILE"));
|
||||||
|
try {
|
||||||
|
String missionsData = FileCopyUtils.copyToString(new FileReader(missionsFile));
|
||||||
|
return ResponseEntity.ok()
|
||||||
|
.header("Content-Type","application/json")
|
||||||
|
.body(missionsData);
|
||||||
|
} catch (IOException e) {
|
||||||
|
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
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;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.ui.Model;
|
|
||||||
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;
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
|
||||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/questions")
|
|
||||||
public class QuestionsController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
UserRepository ur;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
QuizzManager qm;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
QuizzRepository qrepo;
|
|
||||||
|
|
||||||
@GetMapping("/quizz")
|
|
||||||
/*
|
|
||||||
* List all quizz
|
|
||||||
*/
|
|
||||||
public String getQuizz(Model model, Principal p) {
|
|
||||||
User u = null;
|
|
||||||
if (p!=null)
|
|
||||||
u = ur.findByName(p.getName());
|
|
||||||
if(u!=null) {
|
|
||||||
model.addAttribute("quizz",qm.accessibleQuizz(u));
|
|
||||||
}
|
|
||||||
return "quizz.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/forms")
|
|
||||||
/*
|
|
||||||
* List all forms started or finished by the user
|
|
||||||
*/
|
|
||||||
public String getForms() {
|
|
||||||
return "forms.html";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/form/{q}")
|
|
||||||
public String formpage(@PathVariable("q") long quizzId, Principal p, Model m) {
|
|
||||||
if (p==null)
|
|
||||||
return "redirect:/login?restricted";
|
|
||||||
m.addAttribute("formid", quizzId);
|
|
||||||
Quizz q = qrepo.getReferenceById(quizzId);
|
|
||||||
m.addAttribute("quizzLength",q.getQuestionCount());
|
|
||||||
|
|
||||||
return "form";
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/question/{q}")
|
|
||||||
public ResponseEntity<JsonNode> question(@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.next(u, quizzId);
|
|
||||||
return new ResponseEntity<>(out, HttpStatus.OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/answer/{q}")
|
|
||||||
public ResponseEntity<JsonNode> answer(@PathVariable("q") long quizzId, @RequestBody JsonNode data, Principal p) {
|
|
||||||
User u = null;
|
|
||||||
if (p!=null)
|
|
||||||
u = ur.findByName(p.getName());
|
|
||||||
JsonNode out = qm.answer(u, quizzId, data);
|
|
||||||
return 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
alter table quizz add column "is_complete" BOOLEAN NOT NULL DEFAULT FALSE;
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
alter table role_privileges drop constraint role_privileges_privileges_check;
|
||||||
|
alter table role_privileges add constraint role_privileges_privileges_check
|
||||||
|
check (privileges in ('LIST_USERS','ADD_USERS','LIST_QUIZZ','CREATE_QUIZZ','VIEW_ALL_FORMS'));
|
||||||
|
|
||||||
|
do $$
|
||||||
|
begin
|
||||||
|
if exists (select id from roles where "name" = 'ADMIN')
|
||||||
|
then
|
||||||
|
insert into role_privileges VALUES
|
||||||
|
((select id from roles where "name" = 'ADMIN'),'CREATE_QUIZZ'),
|
||||||
|
((select id from roles where "name" = 'ADMIN'),'VIEW_ALL_FORMS');
|
||||||
|
end if;
|
||||||
|
end
|
||||||
|
$$
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table if exists quizz add column public_question_count integer default NULL;
|
||||||
|
alter table if exists quizz drop column is_public;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
create table user_privileges (id bigint not null, privileges varchar(255) not null check (privileges in ('LIST_USERS','ADD_USERS','LIST_QUIZZ','CREATE_QUIZZ','VIEW_ALL_FORMS')), primary key (id, privileges));
|
||||||
|
drop table role_privileges cascade;
|
||||||
|
drop table roles cascade;
|
||||||
|
alter table if exists users alter column password drop not null;
|
||||||
|
alter table if exists users add column oidc_id varchar(255);
|
||||||
|
alter table if exists users drop constraint if exists UKs3fyv3dn1pq639igamsrq5vhj;
|
||||||
|
alter table if exists users add constraint UKs3fyv3dn1pq639igamsrq5vhj unique (oidc_id);
|
||||||
|
alter table if exists user_privileges add constraint FKqltalsck5q21fw7vcpsriacst foreign key (id) references users;
|
||||||
@@ -1,3 +1,22 @@
|
|||||||
|
main h1 input, main h1 span{
|
||||||
|
font-size: 30pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: teal;
|
||||||
|
}
|
||||||
|
|
||||||
|
main h1 button{
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
main .buttonbar button{
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
main .buttonbar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
ol#questions-list {
|
ol#questions-list {
|
||||||
display: table;
|
display: table;
|
||||||
border-spacing: 2px;
|
border-spacing: 2px;
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
main h1 input, main h1 span{
|
||||||
|
font-size: 30pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: teal;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#questions-list {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
ol#questions-list li {
|
||||||
|
width: 90%;
|
||||||
|
background-color: lemonchiffon;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* DCC */
|
||||||
|
ol#questions-list li.dcc-box {
|
||||||
|
background-color: gainsboro;
|
||||||
|
color: black;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
li.dcc-box h5 {
|
||||||
|
font-size: 12pt;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
main h1 input, main h1 span{
|
||||||
|
font-size: 30pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: teal;
|
||||||
|
}
|
||||||
|
|
||||||
|
table#forms-table {
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
table#forms-table tr {
|
||||||
|
width: 90%;
|
||||||
|
background-color: lemonchiffon;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* DCC */
|
||||||
|
table#forms-table tbody tr.done-true {
|
||||||
|
background-color: rgb(161, 161, 161);
|
||||||
|
color: black;
|
||||||
|
font-size: 12pt;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
table#forms-table tbody tr {
|
||||||
|
background-color: gainsboro;
|
||||||
|
color: black;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
table#forms-table thead tr {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
@@ -44,13 +44,13 @@ nav .navbar .menu{
|
|||||||
}
|
}
|
||||||
main{
|
main{
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 100vh;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #96c7e8;
|
color: #96c7e8;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
margin-top: 115px;
|
||||||
}
|
}
|
||||||
.button a{
|
.button a{
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
import xml.etree.ElementTree
|
||||||
|
import copy
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import pathlib
|
||||||
|
|
||||||
|
thecrewSvgFile = sys.argv[1]
|
||||||
|
thecrewModifiersSvgFile = sys.argv[2]
|
||||||
|
|
||||||
|
def withShownHidden(origXML,toShow,toHide,recolorize={}):
|
||||||
|
|
||||||
|
outXML = copy.deepcopy(origXML)
|
||||||
|
|
||||||
|
for th in toHide:
|
||||||
|
node = outXML.find(f".//{{http://www.w3.org/2000/svg}}*[@{{http://www.inkscape.org/namespaces/inkscape}}label='{th}']")
|
||||||
|
if node is None:
|
||||||
|
print(f"Could not hide node with label {th} : Not Found")
|
||||||
|
exit(1)
|
||||||
|
node.attrib['style'] = "display: none"
|
||||||
|
for ts in toShow:
|
||||||
|
node = outXML.find(f".//{{http://www.w3.org/2000/svg}}*[@{{http://www.inkscape.org/namespaces/inkscape}}label='{ts}']")
|
||||||
|
if node is None:
|
||||||
|
print(f"Could not show node with label {ts} : Not Found")
|
||||||
|
exit(1)
|
||||||
|
nodes = outXML.findall(f".//{{http://www.w3.org/2000/svg}}*[@style]")
|
||||||
|
for node in nodes:
|
||||||
|
for rc in recolorize:
|
||||||
|
node.attrib['style'] = node.attrib['style'].replace(rc,recolorize[rc])
|
||||||
|
return outXML
|
||||||
|
|
||||||
|
def inkscapeToPng(fi,fo):
|
||||||
|
subprocess.run(["inkscape","--export-type","png","--export-dpi","300","--export-filename",fo,fi])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Reading file:
|
||||||
|
origXML = xml.etree.ElementTree.parse(thecrewSvgFile)
|
||||||
|
|
||||||
|
suits = ["Hearts","Spades","Diamonds","Clubs","Square","Circle","Triangle","Cross"]
|
||||||
|
suitColors = ["e36a6a","545454","e36a6a","545454","e87caf","0085ca","94c11e","e6b436"]
|
||||||
|
numbers = ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine"]
|
||||||
|
modifiers = numbers + ["Omega","OneArrow","TwoArrow","ThreeArrow","FourArrow"]
|
||||||
|
|
||||||
|
for s,sc in zip(suits,suitColors):
|
||||||
|
for n in numbers:
|
||||||
|
outFile = f"{n.lower()}-of-{s.lower()}.png"
|
||||||
|
toShow = [s,n]
|
||||||
|
toHide = list(set(suits+numbers) - set(toShow))
|
||||||
|
out = withShownHidden(origXML,toShow,toHide, recolorize = {
|
||||||
|
"123456": sc
|
||||||
|
})
|
||||||
|
out.write('card.tmp.svg')
|
||||||
|
inkscapeToPng('card.tmp.svg',outFile)
|
||||||
|
|
||||||
|
origXML = xml.etree.ElementTree.parse(thecrewModifiersSvgFile)
|
||||||
|
for m in modifiers:
|
||||||
|
outFile = f"{m.lower()}.png"
|
||||||
|
toShow = [m]
|
||||||
|
toHide = list(set(modifiers) - set(toShow))
|
||||||
|
out = withShownHidden(origXML,toShow,toHide)
|
||||||
|
out.write('card.tmp.svg')
|
||||||
|
inkscapeToPng('card.tmp.svg',outFile)
|
||||||
|
pathlib.Path.unlink('card.tmp.svg')
|
||||||
|
print("Done !")
|
||||||
|
|
||||||
@@ -0,0 +1,230 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="10mm"
|
||||||
|
height="10mm"
|
||||||
|
viewBox="0 0 10 10"
|
||||||
|
version="1.1"
|
||||||
|
id="svg1"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||||
|
sodipodi:docname="thecrew-modifiers.svg"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
showgrid="true"
|
||||||
|
inkscape:zoom="11.313708"
|
||||||
|
inkscape:cx="17.191534"
|
||||||
|
inkscape:cy="25.411651"
|
||||||
|
inkscape:window-width="1680"
|
||||||
|
inkscape:window-height="1022"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="0"
|
||||||
|
inkscape:window-maximized="1"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showguides="true"><inkscape:grid
|
||||||
|
id="grid8"
|
||||||
|
units="mm"
|
||||||
|
originx="0"
|
||||||
|
originy="0"
|
||||||
|
spacingx="0.99999997"
|
||||||
|
spacingy="0.99999997"
|
||||||
|
empcolor="#0099e5"
|
||||||
|
empopacity="0.30196078"
|
||||||
|
color="#0099e5"
|
||||||
|
opacity="0.14901961"
|
||||||
|
empspacing="5"
|
||||||
|
enabled="true"
|
||||||
|
visible="true" /></sodipodi:namedview><defs
|
||||||
|
id="defs1"><inkscape:path-effect
|
||||||
|
effect="tiling"
|
||||||
|
id="path-effect34"
|
||||||
|
is_visible="true"
|
||||||
|
lpeversion="1.3.1"
|
||||||
|
unit="px"
|
||||||
|
seed="1;1"
|
||||||
|
lpesatellites=""
|
||||||
|
num_rows="17"
|
||||||
|
num_cols="1"
|
||||||
|
gapx="-4"
|
||||||
|
gapy="0"
|
||||||
|
offset="0"
|
||||||
|
offset_type="false"
|
||||||
|
scale="0"
|
||||||
|
rotate="0"
|
||||||
|
mirrorrowsx="false"
|
||||||
|
mirrorrowsy="false"
|
||||||
|
mirrorcolsx="false"
|
||||||
|
mirrorcolsy="false"
|
||||||
|
mirrortrans="false"
|
||||||
|
shrink_interp="false"
|
||||||
|
split_items="false"
|
||||||
|
link_styles="false"
|
||||||
|
interpolate_scalex="false"
|
||||||
|
interpolate_scaley="true"
|
||||||
|
interpolate_rotatex="false"
|
||||||
|
interpolate_rotatey="true"
|
||||||
|
random_scale="false"
|
||||||
|
random_rotate="false"
|
||||||
|
random_gap_y="false"
|
||||||
|
random_gap_x="false"
|
||||||
|
transformorigin="" />
|
||||||
|
|
||||||
|
|
||||||
|
<clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath242"><path
|
||||||
|
style="opacity:1;fill:#415c12;fill-opacity:1;stroke:#000000;stroke-width:0.671709;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1709.2189,406.13284 c -4.2633,2.90065 -1.7186,8.85042 -2.4113,13.16379 0,48.65635 0,97.31269 0,145.96904 0.1247,4.23038 5.9488,5.23308 7.8742,1.65405 7.587,-10.10644 18.2878,-16.3931 28.9918,-22.67885 22.5621,-14.49643 36.3572,-41.58453 34.2383,-68.39796 -1.6209,-30.89618 -24.6859,-59.00274 -54.3368,-67.43023 -4.602,-1.35315 -9.578,-2.61297 -14.3562,-2.27984 z"
|
||||||
|
id="path243" /></clipPath><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath243"><path
|
||||||
|
style="opacity:1;fill:#415c12;fill-opacity:1;stroke:#000000;stroke-width:0.671709;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1568.5723,615.59047 c -3.9338,3.0891 -1.483,8.76578 -2.166,13.04576 0,44.19669 0,88.39337 0,132.59005 0.4192,4.82868 5.839,4.15875 9.2599,3.4029 21.5358,-3.23562 41.2278,-16.72175 52.2964,-35.44977 6.5292,-10.63248 13.3334,-21.63647 23.9575,-28.67732 4.2146,-3.42916 9.8684,-4.63414 13.8018,-8.32172 1.9716,-4.68498 -3.5275,-6.40277 -6.75,-8.00905 -14.2712,-7.5491 -24.312,-20.8286 -32.1004,-34.61212 -12.2796,-19.18606 -33.756,-32.48789 -56.482,-34.284 -0.6058,0.10456 -1.2115,0.21019 -1.8172,0.31528 z"
|
||||||
|
id="path244" /></clipPath><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath244"><path
|
||||||
|
style="opacity:1;fill:#415c12;fill-opacity:1;stroke:#000000;stroke-width:0.671709;stroke-linecap:butt;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1644.3233,522.44617 c -3.748,3.17056 -1.3791,8.72096 -2.0457,12.98096 0,44.10374 0,88.20747 0,132.31121 0.3605,4.57601 5.4519,4.27785 8.7705,3.47115 36.1672,-4.81331 65.025,-39.63862 63.7253,-75.98111 -0.055,-16.95341 -6.5914,-33.48486 -17.4777,-46.3916 -12.665,-14.7911 -30.87,-25.70564 -50.5877,-26.88931 -0.7949,0.16622 -1.5898,0.33244 -2.3847,0.49866 z"
|
||||||
|
id="path245" /></clipPath><clipPath
|
||||||
|
clipPathUnits="userSpaceOnUse"
|
||||||
|
id="clipPath251"><path
|
||||||
|
style="opacity:1;fill:#415c12;fill-opacity:1;stroke:#000000;stroke-width:0.94994;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1821.5575,363.26619 c -21.4621,-0.3126 -42.5982,7.61778 -59.6293,20.42026 -12.0506,9.58617 -21.3723,22.46414 -34.9745,29.97835 -4.3462,2.44276 -12.837,1.2715 -12.0875,8.27159 0,99.97469 0,199.9494 0,299.92411 0.7288,5.67004 7.1425,3.89104 9.9624,0.92125 59.3638,-40.45963 116.3623,-86.63884 158.6972,-145.27945 21.0582,-29.94486 39.0038,-63.9963 41.8724,-101.08789 1.3831,-21.46882 -1.8003,-43.77497 -13.2705,-62.34701 -17.7689,-29.99071 -51.1684,-51.25706 -86.4957,-50.78858 -1.358,-0.0241 -2.7164,-0.0228 -4.0745,-0.0126 z"
|
||||||
|
id="path251" /></clipPath></defs><g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"><rect
|
||||||
|
style="display:inline;fill:#001aaa;fill-opacity:1;stroke:#001277;stroke-width:0.6;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="rect13"
|
||||||
|
width="9.999999"
|
||||||
|
height="9.999999"
|
||||||
|
x="1.4999962"
|
||||||
|
y="0"
|
||||||
|
transform="translate(-1.4999962)" /><g
|
||||||
|
id="g27"
|
||||||
|
transform="translate(-0.22206006)"
|
||||||
|
inkscape:label="Numbers"
|
||||||
|
style="display:inline;fill:#123456;fill-opacity:1"><path
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 3.0555119,2.5671475 1.36312,-1.36312 h 2.265848 v 5.930925 h 0.704128 v 1.66102 h -3.538695 v -1.606856 h 0.848565 v -4.062278 l -0.839538,0.839538 z"
|
||||||
|
id="path1"
|
||||||
|
inkscape:label="One" /><path
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 2.6662109,8.689902 h 5.163605 V 7.245537 h -2.310984 l 2.15752,-2.157521 c 0,0 0,-2.390954 0,-2.78943 0,-0.398477 -0.553503,-0.988488 -0.988487,-0.988488 -0.434984,0 -2.554528,0 -3.082817,0 -0.528289,0 -0.990744,0.475441 -0.990744,0.990744 0,0.515304 0,1.821255 0,1.821255 h 1.821254 V 2.695786 h 1.173547 V 4.663734 L 2.6391289,7.63371 Z"
|
||||||
|
id="path17"
|
||||||
|
sodipodi:nodetypes="ccccczzzzccccccc"
|
||||||
|
inkscape:label="Two" /><path
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 2.7361059,3.5603965 v -2.323504 h 5.183202 v 1.391549 l -1.200052,1.404316 v 0.165964 c 0.677046,0 1.027704,0.471789 1.027704,1.027704 0,0 0,1.715691 0,2.214989 0,0.499298 -0.619358,1.321693 -1.222393,1.321693 -0.603035,0 -2.042752,0 -2.537343,0 -0.494591,0 -1.462412,-0.406745 -1.462412,-0.943624 v -1.514288 h 1.998605 v 0.893655 h 1.212818 v -1.736244 h -1.238351 v -0.919189 l 1.31495,-1.774544 h -1.366016 v 0.80429 z"
|
||||||
|
id="path18"
|
||||||
|
sodipodi:nodetypes="cccccczzzzzccccccccccc"
|
||||||
|
inkscape:label="Three" /><path
|
||||||
|
id="path19"
|
||||||
|
style="display:inline;opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 5.0597959,1.1994565 -2.636015,3.538802 v 1.697054 h 2.906799 v 0.75861 h -0.8847 v 1.606621 h 3.556372 v -1.606621 h -0.77618 v -0.812353 h 0.794267 v -1.498617 h -0.830441 v -3.683496 z m 0.234611,1.841748 v 1.931665 h -1.390096 z"
|
||||||
|
inkscape:label="Four" /><path
|
||||||
|
style="display:inline;opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 2.6432259,1.221114 h 5.119369 v 1.634113 h -3.076728 v 0.817056 c 0,0 1.346344,0 1.953275,0 0.606931,0 1.161752,0.391594 1.161752,1.161752 0,0.770159 0,2.127308 0,2.859698 0,0.664285 -0.426838,1.085153 -1.085152,1.085153 -0.658315,0 -2.06756,0 -2.821398,0 -0.753838,0 -1.212818,-0.602587 -1.212818,-1.110686 0,-0.508099 0,-1.480915 0,-1.480915 h 1.889442 v 1.136219 h 1.289417 V 5.306396 h -3.204393 z"
|
||||||
|
id="path20"
|
||||||
|
sodipodi:nodetypes="ccccczzszzzccccccc"
|
||||||
|
inkscape:label="Five" /><g
|
||||||
|
id="g22"
|
||||||
|
transform="translate(-9.1161521,-0.1206331)"
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.00036;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
inkscape:label="Six"><path
|
||||||
|
id="path21"
|
||||||
|
style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 12.881901,1.381311 c -0.879342,0 -1.543575,0.2853348 -1.543575,1.5435751 0,1.2582403 -0.0052,3.6036771 -0.0052,4.6632812 0,1.0596041 0.68622,1.2510864 1.413351,1.2510864 h 2.784746 C 14.95819,7.5670265 15.64519,7.0621748 16.493018,6.698106 V 5.3712484 c 0,-0.5323266 -0.606087,-1.1647868 -1.164787,-1.1647868 H 13.43274 v -1.317749 h 1.173572 V 3.6111491 H 16.36176 V 2.5771037 c 0,-0.726466 -0.581711,-1.1957927 -1.195793,-1.1957927 h -2.284098 z m 0.550871,4.3511556 h 1.236617 v 1.6066203 h -1.236617 z"
|
||||||
|
sodipodi:nodetypes="czzzcczzccccczzzcccccc" /><circle
|
||||||
|
style="display:inline;opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="path22"
|
||||||
|
cx="16.532625"
|
||||||
|
cy="8.0492821"
|
||||||
|
r="0.81067312" /></g><path
|
||||||
|
style="display:inline;opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="M 2.4644949,3.68505 V 1.157282 h 5.51513 v 1.200051 l -2.195839,6.485385 h -2.37457 l 2.37457,-6.000258 h -1.404315 v 0.868123 z"
|
||||||
|
id="path23"
|
||||||
|
inkscape:label="Seven" /><path
|
||||||
|
id="path24"
|
||||||
|
style="display:inline;opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 3.6048469,1.2694785 c -0.642741,0 -0.893486,0.525011 -0.893486,1.092439 v 1.715141 c 0,0.509739 0.437201,0.725072 0.83044,0.83044 -0.420935,0.11279 -0.927592,0.388208 -0.927592,1.065052 v 1.541508 c 0,0.661002 0.58919,1.216463 1.216464,1.216463 h 3.010669 c 0.485027,0 0.98857,-0.369975 0.98857,-0.98857 v -1.86862 c 0,-0.480076 -0.439475,-0.806637 -0.941028,-0.941028 0.465545,-0.124742 0.873332,-0.390235 0.873332,-0.873332 v -1.566312 c 0,-0.482508 -0.221212,-1.223181 -1.024744,-1.223181 z m 1.038179,1.489314 h 1.209745 v 1.453658 h -1.209745 z m -0.02687,2.825668 h 1.272791 v 1.634009 h -1.272791 z"
|
||||||
|
inkscape:label="Eight" /><g
|
||||||
|
id="g25"
|
||||||
|
transform="rotate(180,3.797988,10.438783)"
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
inkscape:label="Nine"><g
|
||||||
|
id="g26"
|
||||||
|
transform="translate(-11.208993,10.902596)"
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-dasharray:none;stroke-opacity:1"><path
|
||||||
|
id="path25"
|
||||||
|
style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 12.881901,1.381311 c -0.117873,0 -0.23188,0.00513 -0.340941,0.017037 0.39752,1.1466262 -0.417494,1.7180355 -1.202634,1.6669695 0,1.2582403 -0.0052,3.4632455 -0.0052,4.5228496 0,1.0596041 0.68622,1.2510864 1.413351,1.2510864 h 2.68996 c 0.80934,0 1.056581,-0.6103529 1.056581,-1.2994692 V 5.3712484 c 0,-0.5323266 -0.606087,-1.1647868 -1.164787,-1.1647868 H 13.43274 v -1.317749 h 1.173572 V 3.6111491 H 16.36176 V 2.5771037 c 0,-0.726466 -0.581711,-1.1957927 -1.195793,-1.1957927 h -2.284098 z m 0.550871,4.3511556 h 1.236617 v 1.6066203 h -1.236617 z"
|
||||||
|
sodipodi:nodetypes="ccczzsszzccccczzzcccccc" /><circle
|
||||||
|
style="opacity:1;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.6;stroke-linecap:square;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle25"
|
||||||
|
cx="11.483473"
|
||||||
|
cy="1.9213587"
|
||||||
|
r="0.81067312" /></g></g><path
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.600001;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
d="m 1.22206,8.0000002 v 0.9999999 h 2.9999999 c 0,-2.9999998 -1.9999999,-6.470833 1.0000002,-6.4708329 2.9999997,0 0.9999998,3.4708331 0.9999998,6.4708329 H 9.2220597 V 8.0000002 h -2 c 0,0 1.9999999,-4.470833 0,-6.4708331 -0.9999998,-0.99999998 -2.9999999,-1.00000012 -3.9999999,0 -1.9999998,2 0,6.4708331 0,6.4708331 z"
|
||||||
|
id="path14"
|
||||||
|
sodipodi:nodetypes="cccsccccsscc"
|
||||||
|
inkscape:label="Omega" /></g><g
|
||||||
|
id="g5"
|
||||||
|
inkscape:label="FourArrow"
|
||||||
|
transform="translate(0.50000393)"
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-opacity:1"><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 4.9999997,0.99999995 2.9999998,4.9999997 4.9999997,8.9999996 H 3.9999999 l -2,-3.9999999 2,-3.99999975 z"
|
||||||
|
id="path2" /><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 6.9999996,0.99999997 4.9999997,4.9999997 6.9999996,8.9999996 H 5.9999998 l -2,-3.9999999 2,-3.99999973 z"
|
||||||
|
id="path3" /><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 8.9999997,0.99999997 6.9999998,4.9999997 8.9999997,8.9999996 H 7.9999999 l -2,-3.9999999 2,-3.99999973 z"
|
||||||
|
id="path4" /><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 2.9999997,0.99999997 0.99999984,4.9999997 2.9999997,8.9999996 H 1.9999999 L -6.3578292e-8,4.9999997 1.9999999,0.99999997 Z"
|
||||||
|
id="path5" /></g><g
|
||||||
|
id="g9"
|
||||||
|
inkscape:label="ThreeArrow"
|
||||||
|
transform="translate(-0.49999605)"
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-opacity:1"><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 4.9999997,0.99999995 2.9999998,4.9999997 4.9999997,8.9999996 H 3.9999999 l -2,-3.9999999 2,-3.99999975 z"
|
||||||
|
id="path6" /><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 6.9999996,0.99999997 4.9999997,4.9999997 6.9999996,8.9999996 H 5.9999998 l -2,-3.9999999 2,-3.99999973 z"
|
||||||
|
id="path7" /><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 8.9999997,0.99999997 6.9999998,4.9999997 8.9999997,8.9999996 H 7.9999999 l -2,-3.9999999 2,-3.99999973 z"
|
||||||
|
id="path8" /></g><g
|
||||||
|
id="g11"
|
||||||
|
inkscape:label="TwoArrow"
|
||||||
|
transform="translate(-1.499996)"
|
||||||
|
style="display:inline;fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-opacity:1"><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 6.9999996,0.99999997 4.9999997,4.9999997 6.9999996,8.9999996 H 5.9999998 l -2,-3.9999999 2,-3.99999973 z"
|
||||||
|
id="path10" /><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 8.9999997,0.99999997 6.9999998,4.9999997 8.9999997,8.9999996 H 7.9999999 l -2,-3.9999999 2,-3.99999973 z"
|
||||||
|
id="path11" /></g><g
|
||||||
|
id="g13"
|
||||||
|
inkscape:label="OneArrow"
|
||||||
|
transform="translate(-1.4999962)"
|
||||||
|
style="display:inline"><path
|
||||||
|
style="fill:#ffd700;fill-opacity:1;stroke:#d6b500;stroke-width:0.3;stroke-linejoin:round;stroke-opacity:1"
|
||||||
|
d="M 7.9999998,1.0000002 5.9999999,4.9999999 7.9999998,8.9999998 H 7 L 5,4.9999999 7,1.0000002 Z"
|
||||||
|
id="path13" /></g></g></svg>
|
||||||
|
After Width: | Height: | Size: 16 KiB |
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 91 KiB |
@@ -15,11 +15,11 @@
|
|||||||
<div class="buttonbox" id="answer-select">
|
<div class="buttonbox" id="answer-select">
|
||||||
<button class="squared-button bcol1" id="answer-select-duo">Duo</button>
|
<button class="squared-button bcol1" id="answer-select-duo">Duo</button>
|
||||||
<button class="squared-button bcol2" id="answer-select-carre">Carré</button>
|
<button class="squared-button bcol2" id="answer-select-carre">Carré</button>
|
||||||
<button class="squared-button bcol3" id="answer-select-cache">Cache</button>
|
<button class="squared-button bcol3" id="answer-select-cash">Cash</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttonbox" id="answer-cache">
|
<div class="buttonbox" id="answer-cash">
|
||||||
<textarea class="squared-input" id="answer-cache-input" type="text"></textarea>
|
<textarea class="squared-input" id="answer-cash-input" type="text"></textarea>
|
||||||
<button class="squared-button bcol4" id="answer-cache-button">Envoyer la réponse</button>
|
<button class="squared-button bcol4" id="answer-cash-button">Envoyer la réponse</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttonbox" id="answer-duo">
|
<div class="buttonbox" id="answer-duo">
|
||||||
<button class="squared-button bcol1" id="answer-duo-button-1"></button>
|
<button class="squared-button bcol1" id="answer-duo-button-1"></button>
|
||||||
@@ -47,10 +47,13 @@
|
|||||||
console.log(txt)
|
console.log(txt)
|
||||||
$("#error-textbox").text(txt)
|
$("#error-textbox").text(txt)
|
||||||
}
|
}
|
||||||
|
function clearerror() {
|
||||||
|
$("#error-textbox").text("")
|
||||||
|
}
|
||||||
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)
|
||||||
@@ -61,23 +64,24 @@
|
|||||||
error(res["message"])
|
error(res["message"])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
clearerror()
|
||||||
$("#question-counter").text((res["index"]+1)+"/"+qlength)
|
$("#question-counter").text((res["index"]+1)+"/"+qlength)
|
||||||
$("#question-text").text(res["data"]["text"])
|
$("#question-text").text(res["data"]["text"])
|
||||||
if(qstep==0){
|
if(qstep==0){
|
||||||
$("#answer-select").show()
|
$("#answer-select").show()
|
||||||
$("#answer-duo").hide()
|
$("#answer-duo").hide()
|
||||||
$("#answer-carre").hide()
|
$("#answer-carre").hide()
|
||||||
$("#answer-cache").hide()
|
$("#answer-cash").hide()
|
||||||
}else if(qstep==1){
|
}else if(qstep==1){
|
||||||
awrs = res["data"]["answers"]
|
awrs = res["data"]["answers"]
|
||||||
|
|
||||||
$("#answer-select").hide()
|
$("#answer-select").hide()
|
||||||
$("#answer-duo").hide()
|
$("#answer-duo").hide()
|
||||||
$("#answer-carre").hide()
|
$("#answer-carre").hide()
|
||||||
$("#answer-cache").hide()
|
$("#answer-cash").hide()
|
||||||
if(typeof awrs === 'undefined'){
|
if(typeof awrs === 'undefined'){
|
||||||
$("#answer-cache").show()
|
$("#answer-cash").show()
|
||||||
$("#answer-cache-input").val("")
|
$("#answer-cash-input").val("")
|
||||||
} else if (awrs.length == 2) {
|
} else if (awrs.length == 2) {
|
||||||
$("#answer-duo").show()
|
$("#answer-duo").show()
|
||||||
$("#answer-duo-button-1").text(res.data.answers[0])
|
$("#answer-duo-button-1").text(res.data.answers[0])
|
||||||
@@ -108,6 +112,7 @@
|
|||||||
success: function(res) {
|
success: function(res) {
|
||||||
console.log(res)
|
console.log(res)
|
||||||
if(res["success"]){
|
if(res["success"]){
|
||||||
|
clearerror()
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
error(res["message"])
|
error(res["message"])
|
||||||
@@ -130,14 +135,15 @@
|
|||||||
success: function(res) {
|
success: function(res) {
|
||||||
console.log(res)
|
console.log(res)
|
||||||
if(res["success"]){
|
if(res["success"]){
|
||||||
|
clearerror()
|
||||||
|
$("#answer-select").hide()
|
||||||
|
$("#answer-duo").hide()
|
||||||
|
$("#answer-carre").hide()
|
||||||
|
$("#answer-cash").hide()
|
||||||
if(qindex+1 >= qlength) {
|
if(qindex+1 >= qlength) {
|
||||||
$("#question-text").text("Plus de questions !")
|
$("#question-text").text("Plus de questions !")
|
||||||
$("#answer-select").hide()
|
|
||||||
$("#answer-duo").hide()
|
|
||||||
$("#answer-carre").hide()
|
|
||||||
$("#answer-cache").hide()
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
$("#question-text").text("Question suivante ...")
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -151,11 +157,11 @@
|
|||||||
$("#answer-select").hide()
|
$("#answer-select").hide()
|
||||||
$("#answer-duo").hide()
|
$("#answer-duo").hide()
|
||||||
$("#answer-carre").hide()
|
$("#answer-carre").hide()
|
||||||
$("#answer-cache").hide()
|
$("#answer-cash").hide()
|
||||||
$("#answer-select-duo").on('click',() => answer1(2))
|
$("#answer-select-duo").on('click',() => answer1(2))
|
||||||
$("#answer-select-carre").on('click',() => answer1(4))
|
$("#answer-select-carre").on('click',() => answer1(4))
|
||||||
$("#answer-select-cache").on('click',() => answer1(0))
|
$("#answer-select-cash").on('click',() => answer1(0))
|
||||||
$("#answer-cache-button").on('click',() => answer2($("#answer-cache-input").val()))
|
$("#answer-cash-button").on('click',() => answer2($("#answer-cash-input").val()))
|
||||||
$("#answer-duo-button-1").on('click',() => answer2(awrs[0]))
|
$("#answer-duo-button-1").on('click',() => answer2(awrs[0]))
|
||||||
$("#answer-duo-button-2").on('click',() => answer2(awrs[1]))
|
$("#answer-duo-button-2").on('click',() => answer2(awrs[1]))
|
||||||
$("#answer-carre-button-1").on('click',() => answer2(awrs[0]))
|
$("#answer-carre-button-1").on('click',() => answer2(awrs[0]))
|
||||||
|
|||||||
@@ -7,11 +7,15 @@
|
|||||||
<body>
|
<body>
|
||||||
<div th:replace="~{header}"/>
|
<div th:replace="~{header}"/>
|
||||||
<main>
|
<main>
|
||||||
|
<h3>Formulaires terminés</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/questions/form?f=132">Formulaire de réponse au quizz numéro 1</a></li>
|
<li th:if="${#lists.isEmpty(finishedForms)}">Vous n'avez fini aucun formulaire !</li>
|
||||||
<li><a href="/questions/form?f=177">Formulaire de réponse au quizz numéro 2</a></li>
|
<li th:each="qf : ${finishedForms}"><a th:href="@{/questions/showform/{id}(id=${qf.id})}">Quizz <span th:text="${qf.quizz.name}"/></a></li>
|
||||||
<li><a href="/questions/form?f=16818">Formulaire de réponse au quizz numéro 3</a></li>
|
</ul>
|
||||||
|
<h3>Formulaires non terminés</h3>
|
||||||
|
<ul>
|
||||||
|
<li th:if="${#lists.isEmpty(openForms)}">Aucun formulaire ici !</li>
|
||||||
|
<li th:each="qf : ${openForms}"><a th:href="@{/questions/form/{id}(id=${qf.quizz.id})}">Quizz <span th:text="${qf.quizz.name}"/></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<nav>
|
<nav>
|
||||||
<div class="navbar">
|
<div class="navbar">
|
||||||
<div class="logo"><a href="/">Misael</a></div>
|
<div class="logo"><a href="/">Misael</a></div>
|
||||||
<div class="welcome" sec:authorize="isAuthenticated()">Logged in as <span sec:authentication="name"/></div>
|
|
||||||
<ul class="menu">
|
<ul class="menu">
|
||||||
<li><a href="/questions/quizz">Quizz</a></li>
|
<li><a href="/questions/quizz">Quizz</a></li>
|
||||||
<li><a href="/questions/forms">Forms</a></li>
|
<li><a href="/questions/forms">Forms</a></li>
|
||||||
|
|||||||
@@ -10,10 +10,30 @@
|
|||||||
<script th:src="@{/webjars/jquery/1.9.1/jquery.min.js}" type="text/javascript"></script>
|
<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>
|
<script th:src="@{/webjars/lodash/4.17.21/lodash.js}" type="text/javascript"></script>
|
||||||
<main>
|
<main>
|
||||||
<h1>Quizz <span id="question-name"></span><button id="edit-question-name">Edit</button></h1>
|
<h1>Quizz
|
||||||
|
<span id="question-name"></span>
|
||||||
|
<input id="edit-question-name"/>
|
||||||
|
<button id="edit-question-name-button">Edit</button>
|
||||||
|
<button id="edit-question-name-confirm">Valider</button>
|
||||||
|
<button id="edit-question-name-cancel">Annuler</button>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<div class="buttonbar">
|
||||||
|
<button class="display-button" id="add-question">Ajouter</button>
|
||||||
|
<button class="display-button" id="reorder-questions">Réordonner</button>
|
||||||
|
<button class="reorder-button" id="reorder-questions-commit">Valider l'ordre</button>
|
||||||
|
<button class="reorder-button" id="reorder-questions-cancel">Annuler</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ol id="questions-list">
|
<ol id="questions-list">
|
||||||
</ol>
|
</ol>
|
||||||
|
|
||||||
|
<div class="buttonbar">
|
||||||
|
<button id="mark-complete">Marquer le quizz comme terminé</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span id="error-textbox" style="color: red"></span>
|
<span id="error-textbox" style="color: red"></span>
|
||||||
|
|
||||||
<script th:inline="javascript">
|
<script th:inline="javascript">
|
||||||
/*<![CDATA[*/
|
/*<![CDATA[*/
|
||||||
var quizzid = /*[[${quizzId}]]*/ -1;
|
var quizzid = /*[[${quizzId}]]*/ -1;
|
||||||
@@ -31,7 +51,16 @@
|
|||||||
"read": readEditBlockBCC
|
"read": readEditBlockBCC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// question position -> question data
|
||||||
questions={}
|
questions={}
|
||||||
|
// Map question id -> question position
|
||||||
|
qpositions={}
|
||||||
|
// the name of the quizz
|
||||||
|
quizzName = ""
|
||||||
|
|
||||||
|
editing = new Set()
|
||||||
|
|
||||||
function getdata() {
|
function getdata() {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/questions/quizz-edit/"+quizzid+"/get",
|
url: "/questions/quizz-edit/"+quizzid+"/get",
|
||||||
@@ -45,79 +74,127 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
questions=res["questions"]
|
questions=res["questions"]
|
||||||
qname=res["name"]
|
quizzName=res["name"]
|
||||||
$("#question-name").text(qname)
|
$("#question-name").text(quizzName)
|
||||||
|
$("#edit-question-name").val(quizzName)
|
||||||
for(var i=0;i<questions.length;i++){
|
for(var i=0;i<questions.length;i++){
|
||||||
qu = questions[i]
|
qpositions[questions[i]["id"]] = i
|
||||||
qid = qu["id"]
|
internalCreateQuestion(i)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
$(`.reorder-button`).hide()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function contentMode(qid) {
|
|
||||||
$(`#question-display-${qid}-content`).show()
|
function internalCreateQuestion(i){
|
||||||
$(`#question-display-${qid}-button-edit`).show()
|
qu = questions[i]
|
||||||
$(`#question-display-${qid}-type-edit`).show()
|
qid = qu["id"]
|
||||||
$(`#question-display-${qid}-edit`).hide()
|
newhtml = `
|
||||||
$(`#question-display-${qid}-validate-edit`).hide()
|
<li id="q-${qid}">
|
||||||
$(`#question-display-${qid}-cancel-edit`).hide()
|
<div class="content-box" id="q-${qid}-content"/>
|
||||||
}
|
<div class="content-box" id="q-${qid}-edit"/>
|
||||||
function editMode(qid) {
|
<div class="button-box">
|
||||||
$(`#question-display-${qid}-content`).hide()
|
<button class="display-button display-button-${qid}" id="q-${qid}-button-edit">Edit</button>
|
||||||
$(`#question-display-${qid}-button-edit`).hide()
|
<button class="display-button display-button-${qid}" id="q-${qid}-type-edit">Type</button>
|
||||||
$(`#question-display-${qid}-type-edit`).hide()
|
<button class="display-button display-button-${qid}" id="q-${qid}-remove">Supprimer</button>
|
||||||
$(`#question-display-${qid}-edit`).show()
|
<button class="edit-button edit-button-${qid}" id="q-${qid}-validate-edit">Valider</button>
|
||||||
$(`#question-display-${qid}-validate-edit`).show()
|
<button class="edit-button edit-button-${qid}" id="q-${qid}-cancel-edit">Annuler</button>
|
||||||
$(`#question-display-${qid}-cancel-edit`).show()
|
<button class="reorder-button" id="q-${qid}-reorder-uup">Uup</button>
|
||||||
}
|
<button class="reorder-button" id="q-${qid}-reorder-up">Up</button>
|
||||||
function lockEdit(qid) {
|
<button class="reorder-button" id="q-${qid}-reorder-down">Down</button>
|
||||||
$(`#question-display-${qid}-edit *`).prop('disabled', true)
|
<button class="reorder-button" id="q-${qid}-reorder-ddown">DDown</button>
|
||||||
$(`#question-display-${qid}-validate-edit`).prop('disabled', true)
|
</div>
|
||||||
$(`#question-display-${qid}-cancel-edit`).prop('disabled', true)
|
</li>
|
||||||
}
|
`
|
||||||
function unlockEdit(qid) {
|
newdom = $('<li/>').html(newhtml).contents()
|
||||||
$(`#question-display-${qid}-edit *`).prop('disabled', false)
|
$("#questions-list").append(newdom)
|
||||||
$(`#question-display-${qid}-validate-edit`).prop('disabled', false)
|
const theqid = qid
|
||||||
$(`#question-display-${qid}-cancel-edit`).prop('disabled', false)
|
$(`#q-${qid}-button-edit`).on('click',e => edit(theqid))
|
||||||
|
$(`#q-${qid}-type-edit`).on('click',e => setType(theqid))
|
||||||
|
$(`#q-${qid}-remove`).on('click',e => removeQuestion(theqid))
|
||||||
|
$(`#q-${qid}-validate-edit`).on('click',e => commitEdit(theqid))
|
||||||
|
$(`#q-${qid}-cancel-edit`).on('click',e => cancelEdit(theqid))
|
||||||
|
$(`#q-${qid}-reorder-uup`).on('click',e => reorderMove(theqid,'uup'))
|
||||||
|
$(`#q-${qid}-reorder-up`).on('click',e => reorderMove(theqid,'up'))
|
||||||
|
$(`#q-${qid}-reorder-down`).on('click',e => reorderMove(theqid,'down'))
|
||||||
|
$(`#q-${qid}-reorder-ddown`).on('click',e => reorderMove(theqid,'ddown'))
|
||||||
|
|
||||||
|
newcontent = QTYPES[qu["type"]]["display"](qid,qu["value"])
|
||||||
|
$(`#q-${qid}-content`).replaceWith(newcontent)
|
||||||
|
newedit = QTYPES[qu["type"]]["edit"](qid,qu["value"])
|
||||||
|
$(`#q-${qid}-edit`).replaceWith(newedit)
|
||||||
|
|
||||||
|
contentMode(qid)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setType(i,id) {
|
function contentMode(qid) {
|
||||||
|
$(`#q-${qid}-content`).show()
|
||||||
|
$(`.display-button-${qid}`).show()
|
||||||
|
$(`#q-${qid}-edit`).hide()
|
||||||
|
$(`.edit-button-${qid}`).hide()
|
||||||
|
$('.reorder-button').hide()
|
||||||
|
}
|
||||||
|
function editMode(qid) {
|
||||||
|
$(`#q-${qid}-content`).hide()
|
||||||
|
$(`.display-button-${qid}`).hide()
|
||||||
|
$(`#q-${qid}-edit`).show()
|
||||||
|
$(`.edit-button-${qid}`).show()
|
||||||
|
$('.reorder-button').hide()
|
||||||
|
}
|
||||||
|
function lockEdit(qid) {
|
||||||
|
$(`#q-${qid}-edit *`).prop('disabled', true)
|
||||||
|
$(`.edit-button-${qid}`).prop('disabled', true)
|
||||||
|
}
|
||||||
|
function unlockEdit(qid) {
|
||||||
|
$(`#q-${qid}-edit *`).prop('disabled', false)
|
||||||
|
$(`.edit-button-${qid}`).prop('disabled', false)
|
||||||
|
}
|
||||||
|
function contentNameMode() {
|
||||||
|
$("#question-name").show()
|
||||||
|
$(`#edit-question-name-button`).show()
|
||||||
|
$("#edit-question-name").hide()
|
||||||
|
$(`#edit-question-name-confirm`).hide()
|
||||||
|
$(`#edit-question-name-cancel`).hide()
|
||||||
|
}
|
||||||
|
function editNameMode() {
|
||||||
|
$("#question-name").hide()
|
||||||
|
$(`#edit-question-name-button`).hide()
|
||||||
|
$("#edit-question-name").show()
|
||||||
|
$(`#edit-question-name-confirm`).show()
|
||||||
|
$(`#edit-question-name-cancel`).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function reorderMode() {
|
||||||
|
$(`.display-button`).hide()
|
||||||
|
$(`.edit-button`).hide()
|
||||||
|
$(`.reorder-button`).show()
|
||||||
|
for (let qid of editing) {
|
||||||
|
lockEdit(qid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function unReorderMode() {
|
||||||
|
$(`.reorder-button`).hide()
|
||||||
|
$(`.display-button`).show()
|
||||||
|
for (let qid of editing) {
|
||||||
|
$(`.display-button-${qid}`).hide()
|
||||||
|
$(`.edit-button-${qid}`).show()
|
||||||
|
editMode(qid)
|
||||||
|
unlockEdit(qid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setType(qid) {
|
||||||
//TODO à faire ^^
|
//TODO à faire ^^
|
||||||
}
|
}
|
||||||
|
|
||||||
function edit(id, qid) {
|
|
||||||
|
function edit(qid) {
|
||||||
|
editing.add(qid)
|
||||||
editMode(qid)
|
editMode(qid)
|
||||||
}
|
}
|
||||||
|
|
||||||
function commitEdit(i,qid) {
|
function commitEdit(qid) {
|
||||||
const newvalue = readEditBlockBCC(qid)
|
const newvalue = readEditBlockBCC(qid)
|
||||||
lockEdit(qid)
|
lockEdit(qid)
|
||||||
$.ajax({
|
$.ajax({
|
||||||
@@ -134,34 +211,239 @@
|
|||||||
unlockEdit(qid)
|
unlockEdit(qid)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
i = qpositions[qid]
|
||||||
questions[i]["value"] = newvalue
|
questions[i]["value"] = newvalue
|
||||||
newcontent = QTYPES[questions[i]["type"]]["display"](qid,questions[i]["value"])
|
newcontent = QTYPES[questions[i]["type"]]["display"](qid,questions[i]["value"])
|
||||||
$(`#question-display-${qid}-content`).replaceWith(newcontent)
|
$(`#q-${qid}-content`).replaceWith(newcontent)
|
||||||
|
|
||||||
unlockEdit(qid)
|
unlockEdit(qid)
|
||||||
|
editing.delete(qid)
|
||||||
contentMode(qid)
|
contentMode(qid)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function cancelEdit(i,qid) {
|
function removeQuestion(qid) {
|
||||||
|
$(`#q-${qid}-remove`).prop('disabled',true)
|
||||||
|
if(!confirm(`Voulez-vous vraiment supprimer la question ${qpositions[qid]+1}`)) {
|
||||||
|
$(`#q-${qid}-remove`).prop('disabled', false)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/quizz-edit/"+quizzid+"/remove-question/"+qid,
|
||||||
|
type: "POST",
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
$(`#q-${qid}-remove`).prop('disabled', false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
$(`#q-${qid}-remove`).prop('disabled', false)
|
||||||
|
i = qpositions[qid]
|
||||||
|
qpositions[qid] = null
|
||||||
|
questions.splice(i,1)
|
||||||
|
|
||||||
|
$(`#q-${qid}`).remove()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function cancelEdit(qid) {
|
||||||
|
i = qpositions[qid]
|
||||||
newedit = QTYPES[questions[i]["type"]]["edit"](qid,questions[i]["value"])
|
newedit = QTYPES[questions[i]["type"]]["edit"](qid,questions[i]["value"])
|
||||||
$(`#question-display-${qid}-edit`).replaceWith(newedit)
|
$(`#q-${qid}-edit`).replaceWith(newedit)
|
||||||
|
editing.delete(qid)
|
||||||
contentMode(qid)
|
contentMode(qid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function commitEditName() {
|
||||||
|
const newname = $("#edit-question-name").val()
|
||||||
|
$("#edit-question-name").prop('disabled', true)
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/quizz-edit/"+quizzid+"/set-name",
|
||||||
|
type: "POST",
|
||||||
|
data: newname,
|
||||||
|
dataType: 'json',
|
||||||
|
contentType: 'text/plain',
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
$("#edit-question-name").prop('disabled', false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
quizzName = newname
|
||||||
|
$(`#question-name`).text(quizzName)
|
||||||
|
|
||||||
|
$("#edit-question-name").prop('disabled', false)
|
||||||
|
contentNameMode()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function cancelEditName(i,qid) {
|
||||||
|
$("#edit-question-name").val(quizzName)
|
||||||
|
contentNameMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
function addQuestion() {
|
||||||
|
$("#add-question").prop('disabled', true)
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/quizz-edit/"+quizzid+"/add-question",
|
||||||
|
type: "POST",
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
$("#add-question").prop('disabled', false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
q = {id: res["id"], type: res["type"], value: res["value"]}
|
||||||
|
qpositions[q["id"]] = questions.length
|
||||||
|
questions.push(q)
|
||||||
|
internalCreateQuestion(questions.length-1)
|
||||||
|
$("#add-question").prop('disabled', false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map newindex -> oldindex
|
||||||
|
ordermapping = []
|
||||||
|
|
||||||
|
function startReorder() {
|
||||||
|
ordermapping = new Array(questions.length)
|
||||||
|
for (let i=0;i<questions.length;i++){
|
||||||
|
ordermapping[i] = i
|
||||||
|
}
|
||||||
|
reorderMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
function reorderMove(qid,direction){
|
||||||
|
// Actual position of qid
|
||||||
|
i = ordermapping.indexOf(qpositions[qid])
|
||||||
|
switch(direction) {
|
||||||
|
case "uup":
|
||||||
|
j = 0
|
||||||
|
break
|
||||||
|
case "up":
|
||||||
|
j = Math.max(i-1,0)
|
||||||
|
break
|
||||||
|
case "down":
|
||||||
|
j = Math.min(i+1,ordermapping.length-1)
|
||||||
|
break
|
||||||
|
case "ddown":
|
||||||
|
j = ordermapping.length-1
|
||||||
|
break
|
||||||
|
}
|
||||||
|
moveQuestion(i,j)
|
||||||
|
}
|
||||||
|
// Move question from index i to index j
|
||||||
|
function moveQuestion(i,j) {
|
||||||
|
console.log(`Moving ${i} to ${j}`)
|
||||||
|
if (i == j) return
|
||||||
|
|
||||||
|
// The question we move
|
||||||
|
qid = questions[ordermapping[i]]['id']
|
||||||
|
|
||||||
|
|
||||||
|
oldomi = ordermapping[i]
|
||||||
|
if (i<j){
|
||||||
|
for (let k=i;k<j;k++){
|
||||||
|
ordermapping[k] = ordermapping[k+1]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (let k=i;k>j;k--){
|
||||||
|
ordermapping[k] = ordermapping[k-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ordermapping[j] = oldomi
|
||||||
|
|
||||||
|
if(j==ordermapping.length-1) {
|
||||||
|
$(`#q-${qid}`).appendTo($("#questions-list"))
|
||||||
|
} else {
|
||||||
|
// We want the one _currently_ at position j+1
|
||||||
|
qid2 = questions[ordermapping[j+1]]['id']
|
||||||
|
console.log(`Moving ${qid} before ${qid2}`)
|
||||||
|
$(`#q-${qid}`).insertBefore($(`#q-${qid2}`))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitReorder() {
|
||||||
|
$(".reorder-button").prop('disabled', true)
|
||||||
|
newIdOrders = []
|
||||||
|
for(let p=0;p<ordermapping.length;p++) {
|
||||||
|
newIdOrders[p] = questions[ordermapping[p]]["id"]
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/quizz-edit/"+quizzid+"/reorder-questions",
|
||||||
|
type: "POST",
|
||||||
|
contentType: 'application/json',
|
||||||
|
data: JSON.stringify(newIdOrders),
|
||||||
|
dataType: 'json',
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
$(".reorder-button").prop('disabled', false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// question position -> question data
|
||||||
|
newquestions=new Array(questions.length)
|
||||||
|
newqpositions={}
|
||||||
|
for(let p=0;p<ordermapping.length;p++){
|
||||||
|
newquestions[p] = questions[ordermapping[p]]
|
||||||
|
newqpositions[newquestions[p]['id']] = p
|
||||||
|
}
|
||||||
|
questions = newquestions
|
||||||
|
qpositions = newqpositions
|
||||||
|
$(".reorder-button").prop('disabled', false)
|
||||||
|
unReorderMode()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelReorder() {
|
||||||
|
questionsBlock = $("#questions-list")
|
||||||
|
// We just put all the blocks back in order
|
||||||
|
for (let q of questions) {
|
||||||
|
qid = q["id"]
|
||||||
|
$(`#q-${qid}`).appendTo(questionsBlock)
|
||||||
|
}
|
||||||
|
unReorderMode()
|
||||||
|
}
|
||||||
|
|
||||||
|
function markComplete() {
|
||||||
|
if (confirm("Voulez-vous vraiment marquer ce quizz comme terminé ? Vous ne pourrez plus l'éditer !")) {
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/mark-complete/"+quizzid,
|
||||||
|
type: "POST",
|
||||||
|
success: function(res) {
|
||||||
|
console.log("SUPER")
|
||||||
|
//TODO redirect to the right page
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function makeEditBlockBCC(id,question) {
|
function makeEditBlockBCC(id,question) {
|
||||||
html = `
|
html = `
|
||||||
<div class="content-box dcc-box" id="question-display-${id}-edit">
|
<div class="content-box dcc-box" id="q-${id}-edit">
|
||||||
<h5><input id="question-display-${id}-edit-question"
|
<h5><input id="q-${id}-edit-question"
|
||||||
type="text" value="${_.escape(question["text"])}"/></h5>
|
type="text" value="${_.escape(question["text"])}"/></h5>
|
||||||
<ol>
|
<ol>
|
||||||
<li class="right">☑ <input id="question-display-${id}-edit-good"
|
<li class="right">☑ <input id="q-${id}-edit-good"
|
||||||
type="text" value="${_.escape(question["good"])}"/></li>
|
type="text" value="${_.escape(question["good"])}"/></li>
|
||||||
<li class="wrong">☒ <input id="question-display-${id}-edit-wrong1"
|
<li class="wrong">☒ <input id="q-${id}-edit-wrong1"
|
||||||
type="text" value="${_.escape(question["wrong"][0])}"/></li>
|
type="text" value="${_.escape(question["wrong"][0])}"/></li>
|
||||||
<li class="wrong">☒ <input id="question-display-${id}-edit-wrong2"
|
<li class="wrong">☒ <input id="q-${id}-edit-wrong2"
|
||||||
type="text" value="${_.escape(question["wrong"][1])}"/></li>
|
type="text" value="${_.escape(question["wrong"][1])}"/></li>
|
||||||
<li class="wrong">☒ <input id="question-display-${id}-edit-wrong3"
|
<li class="wrong">☒ <input id="q-${id}-edit-wrong3"
|
||||||
type="text" value="${_.escape(question["wrong"][2])}"/></li>
|
type="text" value="${_.escape(question["wrong"][2])}"/></li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
@@ -170,11 +452,11 @@
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
function readEditBlockBCC(id) {
|
function readEditBlockBCC(id) {
|
||||||
questionText = $(`#question-display-${id}-edit-question`).val()
|
questionText = $(`#q-${id}-edit-question`).val()
|
||||||
goodText = $(`#question-display-${id}-edit-good`).val()
|
goodText = $(`#q-${id}-edit-good`).val()
|
||||||
wrongText1 = $(`#question-display-${id}-edit-wrong1`).val()
|
wrongText1 = $(`#q-${id}-edit-wrong1`).val()
|
||||||
wrongText2 = $(`#question-display-${id}-edit-wrong2`).val()
|
wrongText2 = $(`#q-${id}-edit-wrong2`).val()
|
||||||
wrongText3 = $(`#question-display-${id}-edit-wrong3`).val()
|
wrongText3 = $(`#q-${id}-edit-wrong3`).val()
|
||||||
return {
|
return {
|
||||||
"text": questionText,
|
"text": questionText,
|
||||||
"good": goodText,
|
"good": goodText,
|
||||||
@@ -183,7 +465,7 @@
|
|||||||
}
|
}
|
||||||
function makeAnswerBlockBCC(id,question) {
|
function makeAnswerBlockBCC(id,question) {
|
||||||
html = `
|
html = `
|
||||||
<div class="content-box dcc-box" id="question-display-${id}-content">
|
<div class="content-box dcc-box" id="q-${id}-content">
|
||||||
<h5>${_.escape(question["text"])}</h5>
|
<h5>${_.escape(question["text"])}</h5>
|
||||||
<ol>
|
<ol>
|
||||||
<li class="right">☑ ${_.escape(question["good"])}</li>
|
<li class="right">☑ ${_.escape(question["good"])}</li>
|
||||||
@@ -197,6 +479,15 @@
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentNameMode()
|
||||||
|
$("#reorder-questions").on("click",startReorder)
|
||||||
|
$("#reorder-questions-commit").on("click",commitReorder)
|
||||||
|
$("#reorder-questions-cancel").on("click",cancelReorder)
|
||||||
|
$("#edit-question-name-button").on("click",editNameMode)
|
||||||
|
$("#edit-question-name-confirm").on("click",commitEditName)
|
||||||
|
$("#edit-question-name-cancel").on("click",cancelEditName)
|
||||||
|
$("#add-question").on("click",addQuestion)
|
||||||
|
$("#mark-complete").on("click",markComplete)
|
||||||
getdata()
|
getdata()
|
||||||
</script>
|
</script>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -7,9 +7,22 @@
|
|||||||
<body>
|
<body>
|
||||||
<div th:replace="~{header}"/>
|
<div th:replace="~{header}"/>
|
||||||
<main>
|
<main>
|
||||||
|
<h3>Quizz disponibles</h3>
|
||||||
<ul>
|
<ul>
|
||||||
<li th:if="${#lists.isEmpty(quizz)}">Aucun quizz de disponible malheureusement :(</li>
|
<li th:if="${#lists.isEmpty(answerableQuizz)}">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>
|
<li th:each="q : ${answerableQuizz}"><a th:href="@{/questions/form/{id}(id=${q.id})}">Quizz <span th:text="${q.name}"/></a></li>
|
||||||
|
</ul>
|
||||||
|
<h3>Quizz en édition</h3>
|
||||||
|
<ul>
|
||||||
|
<li th:if="${#lists.isEmpty(editableQuizz)}">Aucun quizz de disponible malheureusement :(</li>
|
||||||
|
<li th:each="q : ${editableQuizz}"><a th:href="@{/questions/quizz-edit/{id}(id=${q.id})}">Quizz <span th:text="${q.name}"/></a></li>
|
||||||
|
</ul>
|
||||||
|
<a sec:authorize="hasAuthority('CREATE_QUIZZ')" th:href="@{/questions/new-quizz}">Nouveau Quizz</a>
|
||||||
|
|
||||||
|
<h3>Quizz terminés</h3>
|
||||||
|
<ul>
|
||||||
|
<li th:if="${#lists.isEmpty(completedQuizz)}">Aucun quizz de disponible malheureusement :(</li>
|
||||||
|
<li th:each="q : ${completedQuizz}"><a th:href="@{/questions/watch/{id}(id=${q.id})}">Quizz <span th:text="${q.name}"/></a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
<link rel="stylesheet" th:href="@{/css/showform.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>
|
||||||
|
|
||||||
|
<ol id="questions-list">
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<span id="error-textbox" style="color: red"></span>
|
||||||
|
|
||||||
|
<script th:inline="javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var formid = /*[[${formId}]]*/ -1;
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
function error(txt) {
|
||||||
|
console.log(txt)
|
||||||
|
$("#error-textbox").text(txt)
|
||||||
|
}
|
||||||
|
QTYPES = {
|
||||||
|
"DCC": makeAnswerBlockBCC,
|
||||||
|
}
|
||||||
|
|
||||||
|
// question position -> question data
|
||||||
|
questiondata=[]
|
||||||
|
|
||||||
|
function getdata() {
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/getformdata/"+formid,
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
questiondata=res["data"]
|
||||||
|
|
||||||
|
for(let qd of questiondata) {
|
||||||
|
content = QTYPES[qd["type"]](qd["qid"],qd["qvalue"],qd["avalue"])
|
||||||
|
$(`#questions-list`).append(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAnswerBlockBCC(id,qvalue,avalue) {
|
||||||
|
switch(avalue["type"]){
|
||||||
|
case 4:
|
||||||
|
dcc = "carré"
|
||||||
|
break
|
||||||
|
case 2:
|
||||||
|
dcc = "duo"
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
dcc = "cash"
|
||||||
|
}
|
||||||
|
html = `
|
||||||
|
<li class="dcc-box" id="q-${id}">
|
||||||
|
<h5>${_.escape(qvalue["text"])}</h5>
|
||||||
|
Réponse ${dcc}: ${_.escape(avalue["answer"])}
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
out = $('<li/>').html(html).contents()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
getdata()
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width" />
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
|
<title>The Crew</title>
|
||||||
|
<style>
|
||||||
|
form {
|
||||||
|
background: #CCCCCC;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bordered {
|
||||||
|
border: 1ex solid black;
|
||||||
|
display: inline-block;
|
||||||
|
margin: 1ex;
|
||||||
|
padding: 1ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.card.done {
|
||||||
|
opacity: .3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<form id="thecrew-config-form">
|
||||||
|
<label for="thecrew-player1">Player 1</label><input id="thecrew-player1" type="text"/><br/>
|
||||||
|
<label for="thecrew-player2">Player 2</label><input id="thecrew-player2" type="text"/><br/>
|
||||||
|
<label for="thecrew-player3">Player 3</label><input id="thecrew-player3" type="text"/><br/>
|
||||||
|
<label for="thecrew-player4">Player 4</label><input id="thecrew-player4" type="text"/><br/>
|
||||||
|
<label for="thecrew-player5">Player 5</label><input id="thecrew-player5" type="text"/><br/>
|
||||||
|
<label for="thecrew-cardtype">Cartes classiques</label><input id="thecrew-cardtype" type="checkbox"/><br/>
|
||||||
|
<input id="thecrew-validate" type="submit" value="Valider"/>
|
||||||
|
</form>
|
||||||
|
<form id="thecrew-run-form">
|
||||||
|
<select id="thecrew-level" name="thecrew-level">
|
||||||
|
</select>
|
||||||
|
<input id="thecrew-run" type="submit" value="Démarrer la partie"/>
|
||||||
|
</form>
|
||||||
|
<span id="thecrew-question">Sélectionnez les paramètres de base</span>
|
||||||
|
<div id="thecrew-select-captain">
|
||||||
|
</div>
|
||||||
|
<div id="thecrew-select-tasks">
|
||||||
|
</div>
|
||||||
|
<div id="thecrew-game">
|
||||||
|
<dl id="thecrew-special-rules"></dl>
|
||||||
|
<ul id="thecrew-assigned-tasks"></ul>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script th:inline="javascript">
|
||||||
|
// Helpers
|
||||||
|
function listProd(l1,l2) {
|
||||||
|
var o = []
|
||||||
|
for (i in l1) {
|
||||||
|
for (j in l2) {
|
||||||
|
o.push([l1[i],l2[j]])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
function emptyListList(k) {
|
||||||
|
var o = []
|
||||||
|
for (let i=0;i<k;i++) {
|
||||||
|
o.push([])
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
function takeNRandom(l,n) {
|
||||||
|
let ll = l.slice()
|
||||||
|
let o = []
|
||||||
|
for (let i=0; i<n; i++) {
|
||||||
|
k = Math.floor(Math.random() * ll.length)
|
||||||
|
o.push(ll[k])
|
||||||
|
ll.splice(k,1)
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
function joinLists(l1,l2) {
|
||||||
|
let ll = l1.slice()
|
||||||
|
ll.push.apply(ll,l2)
|
||||||
|
return ll
|
||||||
|
}
|
||||||
|
function makeCardImg(l) {
|
||||||
|
s = l[1]
|
||||||
|
n = l[0]
|
||||||
|
b = l[2]
|
||||||
|
i = numbers.indexOf(n)
|
||||||
|
tn = numbersText[i]
|
||||||
|
cardFileName=tn+"-of-"+s+".png"
|
||||||
|
out = $('<div/>')
|
||||||
|
.addClass('bordered')
|
||||||
|
.addClass('card')
|
||||||
|
$('<img/>')
|
||||||
|
.attr('src','/thecrew/'+cardFileName)
|
||||||
|
.appendTo(out)
|
||||||
|
if(b){
|
||||||
|
modifierFileName=modifierMap[b].toLowerCase()+".png"
|
||||||
|
$('<img/>')
|
||||||
|
.attr('src','/thecrew/'+modifierFileName)
|
||||||
|
.appendTo(out)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
const thecrewSuits = ['square','triangle','circle','cross']
|
||||||
|
const regularSuits = ['hearts','spades','clubs','diamonds']
|
||||||
|
const numbers = ['1','2','3','4','5','6','7','8','9']
|
||||||
|
const numbersText = ['one','two','three','four','five','six','seven','eight','nine']
|
||||||
|
const trumpNumbers = ['1','2','3','4']
|
||||||
|
const otherThecrewCards = listProd(numbers,thecrewSuits)
|
||||||
|
const otherRegularCards = listProd(numbers,regularSuits)
|
||||||
|
const trumpCards = listProd(trumpNumbers, [null])
|
||||||
|
const allCards = joinLists
|
||||||
|
const modifierMap = {
|
||||||
|
">": "onearrow",
|
||||||
|
">>": "twoarrow",
|
||||||
|
">>>": "threearrow",
|
||||||
|
">>>>": "fourarrow",
|
||||||
|
"1": "one",
|
||||||
|
"2": "two",
|
||||||
|
"3": "three",
|
||||||
|
"4": "four",
|
||||||
|
"5": "five",
|
||||||
|
"Ω": "omega",
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
var missionsData = {}
|
||||||
|
$.getJSON('missions.json',function(data) {
|
||||||
|
missionsData = data
|
||||||
|
for (md in missionsData['missions']) {
|
||||||
|
$('<option/>')
|
||||||
|
.data('thecrew-mission',md)
|
||||||
|
.text(md)
|
||||||
|
.appendTo('#thecrew-level')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function makeMissionRules(i) {
|
||||||
|
var out = {}
|
||||||
|
var missionRules = missionsData['missions'][i]
|
||||||
|
out['cards'] = takeNRandom(otherCards, missionRules['cards'].length)
|
||||||
|
for (c in out['cards']) {
|
||||||
|
out['cards'][c].push(missionRules['cards'][c])
|
||||||
|
}
|
||||||
|
out['specialRules'] = {}
|
||||||
|
for (mr in missionRules['special']) {
|
||||||
|
let specialRule = missionRules['special'][mr]
|
||||||
|
out['specialRules'][specialRule] = missionsData['specialRules'][specialRule]
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
var joueureuses = []
|
||||||
|
var jl = 0
|
||||||
|
var otherCards = otherThecrewCards
|
||||||
|
|
||||||
|
function configGame(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
joueureuses = []
|
||||||
|
for (let i=0;i<5;i++){
|
||||||
|
player = $('#thecrew-player'+(i+1)).val()
|
||||||
|
if (player) {
|
||||||
|
joueureuses.push(player)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
jl = joueureuses.length
|
||||||
|
if ($('#thecrew-cardtype').is(":checked")) {
|
||||||
|
otherCards = otherRegularCards
|
||||||
|
} else {
|
||||||
|
otherCards = otherThecrewCards
|
||||||
|
}
|
||||||
|
$('#thecrew-config-form').hide()
|
||||||
|
$('#thecrew-run-form').show()
|
||||||
|
}
|
||||||
|
$('#thecrew-validate').click(configGame)
|
||||||
|
|
||||||
|
|
||||||
|
var missionRules = null
|
||||||
|
var captain = -1
|
||||||
|
var assignedTasks = emptyListList(jl)
|
||||||
|
var nextToAssign = -1
|
||||||
|
var toAssign = []
|
||||||
|
var specialRules = {}
|
||||||
|
|
||||||
|
function startMission(e) {
|
||||||
|
e.preventDefault()
|
||||||
|
missionNumber = $('#thecrew-level').val()
|
||||||
|
missionRules = makeMissionRules(missionNumber)
|
||||||
|
toAssign = missionRules['cards']
|
||||||
|
specialRules = missionRules['specialRules']
|
||||||
|
captain = -1
|
||||||
|
assignedTasks = emptyListList(jl)
|
||||||
|
nextToAssign = -1
|
||||||
|
|
||||||
|
$('#thecrew-question').text('Qui est capitaine ?')
|
||||||
|
$('#thecrew-select-captain').empty()
|
||||||
|
for (j in joueureuses) {
|
||||||
|
$('<button/>')
|
||||||
|
.text(joueureuses[j])
|
||||||
|
.click(selectedCaptain)
|
||||||
|
.appendTo('#thecrew-select-captain')
|
||||||
|
}
|
||||||
|
$('#thecrew-game').hide()
|
||||||
|
$('#thecrew-select-tasks').hide()
|
||||||
|
$('#thecrew-select-captain').show()
|
||||||
|
}
|
||||||
|
$('#thecrew-run').click(startMission)
|
||||||
|
|
||||||
|
function selectedCaptain(e) {
|
||||||
|
let captainName = e.currentTarget.innerText
|
||||||
|
captain = joueureuses.indexOf(captainName)
|
||||||
|
nextToAssign = captain
|
||||||
|
if (toAssign.length == 1) {
|
||||||
|
assignTask(0)
|
||||||
|
startPlaying()
|
||||||
|
} else if (toAssign.length == 0) {
|
||||||
|
startPlaying()
|
||||||
|
} else {
|
||||||
|
nextAssignTask()
|
||||||
|
}
|
||||||
|
$('#thecrew-select-captain').hide()
|
||||||
|
$('#thecrew-select-tasks').show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function nextAssignTask(){
|
||||||
|
$('#thecrew-question').text(joueureuses[nextToAssign%jl]+': Quelle tâche choisit-tu ?')
|
||||||
|
$('#thecrew-select-tasks').empty()
|
||||||
|
for (at in toAssign) {
|
||||||
|
makeCardImg(toAssign[at])
|
||||||
|
.data("thecrew-task",at)
|
||||||
|
.click(chooseAssignation)
|
||||||
|
.appendTo('#thecrew-select-tasks')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function assignTask(tindex) {
|
||||||
|
assignedTasks[nextToAssign%jl].push(toAssign[tindex])
|
||||||
|
toAssign.splice(tindex,1)
|
||||||
|
nextToAssign += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseAssignation(e) {
|
||||||
|
assignedIndex = $(e.currentTarget).data("thecrew-task")
|
||||||
|
assignTask(assignedIndex)
|
||||||
|
|
||||||
|
if(toAssign.length==1){
|
||||||
|
assignTask(0)
|
||||||
|
startPlaying()
|
||||||
|
} else {
|
||||||
|
nextAssignTask()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startPlaying() {
|
||||||
|
$('#thecrew-question').text("Bon jeu !")
|
||||||
|
$('#thecrew-select-tasks').empty()
|
||||||
|
$('#thecrew-assigned-tasks').empty()
|
||||||
|
$('#thecrew-special-rules').empty()
|
||||||
|
for (at in assignedTasks) {
|
||||||
|
thePlayerLi = $('<li/>').addClass('bordered')
|
||||||
|
$('<h2/>')
|
||||||
|
.text("Tâches de "+joueureuses[at])
|
||||||
|
.appendTo(thePlayerLi)
|
||||||
|
playerList = $('<ul/>')
|
||||||
|
for (tt in assignedTasks[at]) {
|
||||||
|
theTaskLi = $('<li/>')
|
||||||
|
makeCardImg(assignedTasks[at][tt])
|
||||||
|
.data("thecrew-task",[at,tt])
|
||||||
|
.click(taskDone)
|
||||||
|
.appendTo(theTaskLi)
|
||||||
|
theTaskLi.appendTo(playerList)
|
||||||
|
}
|
||||||
|
playerList.appendTo(thePlayerLi)
|
||||||
|
thePlayerLi.appendTo('#thecrew-assigned-tasks')
|
||||||
|
}
|
||||||
|
for (sr in specialRules) {
|
||||||
|
$('<dt/>').text(sr).appendTo('#thecrew-special-rules')
|
||||||
|
$('<dd/>').text(specialRules[sr]).appendTo('#thecrew-special-rules')
|
||||||
|
}
|
||||||
|
$('#thecrew-select-captain').hide()
|
||||||
|
$('#thecrew-select-tasks').hide()
|
||||||
|
$('#thecrew-game').show()
|
||||||
|
}
|
||||||
|
|
||||||
|
function taskDone(e) {
|
||||||
|
ll = $(e.currentTarget).data("thecrew-task")
|
||||||
|
$(e.currentTarget).toggleClass('done')
|
||||||
|
}
|
||||||
|
$('#thecrew-run-form').hide()
|
||||||
|
$('#thecrew-select-captain').hide()
|
||||||
|
$('#thecrew-select-tasks').hide()
|
||||||
|
$('#thecrew-game').hide()
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
<link rel="stylesheet" th:href="@{/css/showformadvancements.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>
|
||||||
|
|
||||||
|
<div class="button-bar">
|
||||||
|
<button id="make-public">Rendre public</button>
|
||||||
|
<button id="make-private">Rendre privé</button>
|
||||||
|
<span id="question-count-number">Nombre de questions accessibles:</span>
|
||||||
|
<button id="set-question-begin"><<</button>
|
||||||
|
<button id="set-question-decrement"><</button>
|
||||||
|
<span id="last-question-number">x</span>
|
||||||
|
<button id="set-question-increment">></button>
|
||||||
|
<button id="set-question-last">>></button>
|
||||||
|
</div>
|
||||||
|
<button id="refresh-data">Refresh</button>
|
||||||
|
<table id="forms-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Nombre de questions répondues</th>
|
||||||
|
<th>Étape</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="forms-body">
|
||||||
|
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<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)
|
||||||
|
}
|
||||||
|
|
||||||
|
formsdata = []
|
||||||
|
pqc = null
|
||||||
|
qCount = null
|
||||||
|
|
||||||
|
allButtons = [
|
||||||
|
$('#refresh-data'),
|
||||||
|
$('#make-public'),
|
||||||
|
$('#make-private'),
|
||||||
|
$('#set-question-begin'),
|
||||||
|
$('#set-question-decrement'),
|
||||||
|
$('#question-count-number'),
|
||||||
|
$('#last-question-number'),
|
||||||
|
$('#set-question-increment'),
|
||||||
|
$('#set-question-last')
|
||||||
|
]
|
||||||
|
|
||||||
|
function activateButtons() {
|
||||||
|
if(pqc === null) {
|
||||||
|
$('#make-public').show()
|
||||||
|
$('#make-private').hide()
|
||||||
|
$('#set-question-begin').hide()
|
||||||
|
$('#set-question-decrement').hide()
|
||||||
|
$('#question-count-number').hide()
|
||||||
|
$('#last-question-number').hide()
|
||||||
|
$('#set-question-increment').hide()
|
||||||
|
$('#set-question-last').hide()
|
||||||
|
} else {
|
||||||
|
$('#make-public').hide()
|
||||||
|
$('#make-private').show()
|
||||||
|
$('#question-count-number').show()
|
||||||
|
$('#set-question-begin').show()
|
||||||
|
$('#set-question-decrement').show()
|
||||||
|
if(pqc == 0) {
|
||||||
|
$('#set-question-begin').prop('disabled', true)
|
||||||
|
$('#set-question-decrement').prop('disabled', true)
|
||||||
|
} else {
|
||||||
|
$('#set-question-begin').prop('disabled', false)
|
||||||
|
$('#set-question-decrement').prop('disabled', false)
|
||||||
|
}
|
||||||
|
$('#last-question-number').show()
|
||||||
|
$('#last-question-number').text(pqc)
|
||||||
|
$('#set-question-last').show()
|
||||||
|
$('#set-question-increment').show()
|
||||||
|
if(pqc == qCount) {
|
||||||
|
$('#set-question-last').prop('disabled', true)
|
||||||
|
$('#set-question-increment').prop('disabled', true)
|
||||||
|
} else {
|
||||||
|
$('#set-question-last').prop('disabled', false)
|
||||||
|
$('#set-question-increment').prop('disabled', false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getdata() {
|
||||||
|
$('#refresh-data').prop('disabled',true)
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/watchdata/"+quizzid,
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
$('#refresh-data').prop('disabled',false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
formsdata=res["data"]
|
||||||
|
pqc = res["publicQuestionCount"]
|
||||||
|
qCount = res["questionCount"]
|
||||||
|
|
||||||
|
$('.form-line').remove()
|
||||||
|
for(let qf of formsdata) {
|
||||||
|
html = `
|
||||||
|
<tr class="form-line done-${qf["done"]}" id="q-${qf["qfid"]}">
|
||||||
|
<td>${qf["username"]}</td>
|
||||||
|
<td>${qf["position"]}</td>
|
||||||
|
<td>${qf["step"]}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
content = $('<li/>').html(html).contents()
|
||||||
|
$(`#forms-body`).append(content)
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const b of allButtons) b.prop('disabled', false)
|
||||||
|
activateButtons()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPQC(n) {
|
||||||
|
for(const b of allButtons) b.prop('disabled', true)
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/set-public-question-count/"+quizzid,
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify({'publicQuestionCount': n}),
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
getdata()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function setPublicQuestionCount(n) {
|
||||||
|
return function (e) { setPQC(n) }
|
||||||
|
}
|
||||||
|
function setPublicQuestionCountLast(n) {
|
||||||
|
return function (e) { setPQC(qCount) }
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPublicQuestionCountRelative(n) {
|
||||||
|
return function (e) { setPQC(pqc + n) }
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#refresh-data').on('click',getdata)
|
||||||
|
$('#make-private').on('click',setPublicQuestionCount(null))
|
||||||
|
$('#make-public').on('click',setPublicQuestionCount(0))
|
||||||
|
$('#set-question-begin').on('click',setPublicQuestionCount(0))
|
||||||
|
$('#set-question-decrement').on('click',setPublicQuestionCountRelative(-1))
|
||||||
|
$('#set-question-increment').on('click',setPublicQuestionCountRelative(+1))
|
||||||
|
$('#set-question-last').on('click',setPublicQuestionCountLast())
|
||||||
|
getdata()
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -3,7 +3,7 @@ package com.bernard.misael;
|
|||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
import com.bernard.misael.questions.QTypes;
|
import com.bernard.misael.quizz.questions.QTypes;
|
||||||
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class MisaelApplicationTests {
|
class MisaelApplicationTests {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ version.org.springframework..spring-jdbc=6.2.3
|
|||||||
|
|
||||||
version.org.springframework.boot..spring-boot-docker-compose=3.4.3
|
version.org.springframework.boot..spring-boot-docker-compose=3.4.3
|
||||||
|
|
||||||
|
version.org.springframework.boot..spring-boot-starter-oauth2-client=4.1.0
|
||||||
|
|
||||||
version.org.webjars..jquery=1.9.1
|
version.org.webjars..jquery=1.9.1
|
||||||
## # available=1.10.1
|
## # available=1.10.1
|
||||||
## # available=1.10.2
|
## # available=1.10.2
|
||||||
|
|||||||
Reference in New Issue
Block a user