Compare commits
27
Commits
master
..
d6cea9649a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6cea9649a | ||
|
|
538231dbd4 | ||
|
|
6de4e43f8d | ||
|
|
270025a1b4
|
||
|
|
655971a000
|
||
|
|
6e5c41431a
|
||
|
|
bbe43cef95
|
||
|
|
d411d41cff
|
||
|
|
08262f3dfd
|
||
|
|
225b5df774
|
||
|
|
c6fa363cd9
|
||
|
|
33d1f1871a
|
||
|
|
c62f070e39
|
||
|
|
8ae7457016
|
||
|
|
be0ec073a3
|
||
|
|
f220c187c1
|
||
|
|
ab84002276
|
||
|
|
15f747807b
|
||
|
|
2c5f529e89
|
||
|
|
b22b514574
|
||
|
|
0ca8ea7408
|
||
|
|
3a7a413c69
|
||
|
|
be01c925a3
|
||
|
|
001ff8b5cb
|
||
|
|
4f3814bd7e
|
||
|
|
90ccb8a00c
|
||
|
|
af5248faed
|
@@ -0,0 +1,3 @@
|
|||||||
|
/gradlew text eol=lf
|
||||||
|
*.bat text eol=crlf
|
||||||
|
*.jar binary
|
||||||
+40
-1
@@ -1 +1,40 @@
|
|||||||
__pycache__/
|
HELP.md
|
||||||
|
.gradle
|
||||||
|
build/
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!**/src/main/**/build/
|
||||||
|
!**/src/test/**/build/
|
||||||
|
.postgres
|
||||||
|
/db-migration.sql
|
||||||
|
/thecrew-missions.json
|
||||||
|
|
||||||
|
### STS ###
|
||||||
|
.apt_generated
|
||||||
|
.classpath
|
||||||
|
.factorypath
|
||||||
|
.project
|
||||||
|
.settings
|
||||||
|
.springBeans
|
||||||
|
.sts4-cache
|
||||||
|
bin/
|
||||||
|
!**/src/main/**/bin/
|
||||||
|
!**/src/test/**/bin/
|
||||||
|
|
||||||
|
### IntelliJ IDEA ###
|
||||||
|
.idea
|
||||||
|
*.iws
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
out/
|
||||||
|
!**/src/main/**/out/
|
||||||
|
!**/src/test/**/out/
|
||||||
|
|
||||||
|
### NetBeans ###
|
||||||
|
/nbproject/private/
|
||||||
|
/nbbuild/
|
||||||
|
/dist/
|
||||||
|
/nbdist/
|
||||||
|
/.nb-gradle/
|
||||||
|
|
||||||
|
### VS Code ###
|
||||||
|
.vscode/
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
FROM gradle:jdk21 AS builder
|
||||||
|
|
||||||
|
WORKDIR /build/
|
||||||
|
ADD src /build/src/
|
||||||
|
ADD build.gradle /build/
|
||||||
|
ADD settings.gradle /build/
|
||||||
|
ADD versions.properties /build/
|
||||||
|
|
||||||
|
|
||||||
|
RUN gradle assemble
|
||||||
|
|
||||||
|
FROM eclipse-temurin:21-noble
|
||||||
|
WORKDIR /opt/
|
||||||
|
ADD "https://download.red-gate.com/maven/release/com/redgate/flyway/flyway-commandline/11.9.1/flyway-commandline-11.9.1-linux-x64.tar.gz" /opt/flyway.tar.gz
|
||||||
|
RUN tar -xvzf /opt/flyway.tar.gz -C /opt/
|
||||||
|
RUN ln -s /opt/flyway-11.9.1/flyway /usr/local/bin
|
||||||
|
|
||||||
|
WORKDIR /app/
|
||||||
|
COPY --from=builder /build/build/libs/misael-beta.jar /app/misael.jar
|
||||||
|
COPY --from=builder /build/build/resources/ /app/resources/
|
||||||
|
ADD launcher.sh /app/
|
||||||
|
|
||||||
|
ENTRYPOINT ["bash","./launcher.sh"]
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<diagram version="1" name="ERD-Misael.erd">
|
||||||
|
<entities>
|
||||||
|
<data-source id="postgres-jdbc-195261858a3-67acab6d92533937">
|
||||||
|
<entity id="1" name="answers" fq-name="misael.answers" order="0" font="Segoe UI:9:0" x="40" y="50">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
<entity id="2" name="questions" fq-name="misael.questions" order="1" font="Segoe UI:9:0" x="279" y="40">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
<entity id="3" name="quizz" fq-name="misael.quizz" order="2" font="Segoe UI:9:0" x="510" y="127">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
<entity id="4" name="quizzf" fq-name="misael.quizzf" order="3" font="Segoe UI:9:0" x="279" y="224">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
<entity id="5" name="roles" fq-name="misael.roles" order="4" font="Segoe UI:9:0" x="709" y="418">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
<entity id="6" name="users" fq-name="misael.users" order="5" font="Segoe UI:9:0" x="709" y="254">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
<entity id="7" name="users_roles" fq-name="misael.users_roles" order="6" font="Segoe UI:9:0" x="510" y="352">
|
||||||
|
<path name="misael"/>
|
||||||
|
<path name="misael"/>
|
||||||
|
</entity>
|
||||||
|
</data-source>
|
||||||
|
</entities>
|
||||||
|
<relations>
|
||||||
|
<relation name="fk54dobrdq2u51m4u8s7kg0as8v" fq-name="misael.answers.fk54dobrdq2u51m4u8s7kg0as8v" type="fk" pk-ref="2" fk-ref="1"/>
|
||||||
|
<relation name="fkq12h25ynjok1m497gwos511te" fq-name="misael.questions.fkq12h25ynjok1m497gwos511te" type="fk" pk-ref="3" fk-ref="2"/>
|
||||||
|
<relation name="fk4ukbg2yaa93gs5nx1s5d9rqu4" fq-name="misael.quizzf.fk4ukbg2yaa93gs5nx1s5d9rqu4" type="fk" pk-ref="3" fk-ref="4"/>
|
||||||
|
<relation name="fkj6m8fwv7oqv74fcehir1a9ffy" fq-name="misael.users_roles.fkj6m8fwv7oqv74fcehir1a9ffy" type="fk" pk-ref="5" fk-ref="7"/>
|
||||||
|
<relation name="fkfeoogns8m4m4hvno1ttqb30wm" fq-name="misael.quizz.fkfeoogns8m4m4hvno1ttqb30wm" type="fk" pk-ref="6" fk-ref="3"/>
|
||||||
|
<relation name="fkuj3h8r3i4lnxukdqobapwbuq" fq-name="misael.quizzf.fkuj3h8r3i4lnxukdqobapwbuq" type="fk" pk-ref="6" fk-ref="4"/>
|
||||||
|
<relation name="fk2o0jvgh89lemvvo17cbqvdxaa" fq-name="misael.users_roles.fk2o0jvgh89lemvvo17cbqvdxaa" type="fk" pk-ref="6" fk-ref="7"/>
|
||||||
|
</relations>
|
||||||
|
</diagram>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Misael
|
||||||
|
|
||||||
|
## For dev
|
||||||
|
You need to setup a postgres server at localhost:5432, with user misael with password misael-dev
|
||||||
|
|
||||||
|
Then, you run the database migration `./gradlew flywayMigrate`
|
||||||
|
|
||||||
|
And finaly we run `./gradlew bootRun`
|
||||||
|
|
||||||
|
## For deployment
|
||||||
|
The compose file is enough.
|
||||||
|
|
||||||
|
`launcher.sh` checks that the migrations are applied and then runs the spring boot server on port 8080
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
buildscript {
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
classpath 'org.flywaydb:flyway-database-postgresql:_'
|
||||||
|
classpath 'org.postgresql:postgresql:_'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
plugins {
|
||||||
|
id 'java'
|
||||||
|
id 'org.springframework.boot'
|
||||||
|
id 'org.flywaydb.flyway'
|
||||||
|
id 'io.spring.dependency-management'
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
group = 'com.bernard'
|
||||||
|
version = 'beta'
|
||||||
|
|
||||||
|
java {
|
||||||
|
toolchain {
|
||||||
|
languageVersion = JavaLanguageVersion.of(21)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation Spring.boot.data.jpa
|
||||||
|
implementation Spring.boot.jdbc
|
||||||
|
implementation Spring.boot.thymeleaf
|
||||||
|
implementation Spring.boot.security
|
||||||
|
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6:_'
|
||||||
|
implementation Spring.boot.web
|
||||||
|
implementation 'org.springframework:spring-jdbc:_'
|
||||||
|
implementation 'org.flywaydb:flyway-core:_'
|
||||||
|
implementation 'org.flywaydb:flyway-database-postgresql:_'
|
||||||
|
implementation Spring.session.jdbc
|
||||||
|
implementation 'jakarta.validation:jakarta.validation-api:_'
|
||||||
|
runtimeOnly 'org.webjars:jquery:_'
|
||||||
|
runtimeOnly 'org.webjars:lodash:_'
|
||||||
|
developmentOnly Spring.boot.devTools
|
||||||
|
runtimeOnly 'org.postgresql:postgresql:_'
|
||||||
|
testImplementation Spring.boot.test
|
||||||
|
testRuntimeOnly 'org.junit.platform:junit-platform-launcher:_'
|
||||||
|
|
||||||
|
implementation 'com.fasterxml.jackson.core:jackson-databind:_'
|
||||||
|
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:_'
|
||||||
|
|
||||||
|
//Lombok
|
||||||
|
compileOnly 'org.projectlombok:lombok:_'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:_'
|
||||||
|
|
||||||
|
testCompileOnly 'org.projectlombok:lombok:_'
|
||||||
|
testAnnotationProcessor 'org.projectlombok:lombok:_'
|
||||||
|
}
|
||||||
|
|
||||||
|
flyway {
|
||||||
|
url = "jdbc:postgresql://127.0.0.1:5432/misael"
|
||||||
|
user = 'misael'
|
||||||
|
password = 'misael-dev'
|
||||||
|
driver = 'org.postgresql.Driver'
|
||||||
|
schemas = ['misael']
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.named('test') {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: 'postgres:latest'
|
||||||
|
environment:
|
||||||
|
- 'POSTGRES_DB=misael'
|
||||||
|
- 'POSTGRES_PASSWORD=misael-dev'
|
||||||
|
- 'POSTGRES_USER=misael'
|
||||||
|
networks:
|
||||||
|
- misanetwork
|
||||||
|
|
||||||
|
misael:
|
||||||
|
image: 'mysaa/misael:latest'
|
||||||
|
build: .
|
||||||
|
environment:
|
||||||
|
- 'MISAEL_DATABASE=jdbc:postgresql://postgres:5432/misael'
|
||||||
|
- 'MISAEL_PASSWORD=misael-dev'
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
networks:
|
||||||
|
- misanetwork
|
||||||
|
|
||||||
|
networks:
|
||||||
|
misanetwork:
|
||||||
|
driver: bridge
|
||||||
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,100 @@
|
|||||||
|
{
|
||||||
|
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" ''
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
}
|
||||||
+2746
File diff suppressed because it is too large
Load Diff
Vendored
BIN
Binary file not shown.
+7
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
@@ -0,0 +1,251 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
Vendored
+94
@@ -0,0 +1,94 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
echo "Checking Database Migration"
|
||||||
|
flyway "-url=$MISAEL_DATABASE" -user=misael "-password=$MISAEL_PASSWORD" -locations="filesystem:resources/main/db/migration/" -schemas=misael migrate
|
||||||
|
|
||||||
|
echo "Running" java -jar misael.jar --spring.datasource.url=$MISAEL_DATABASE --spring.datasource.password=$MISAEL_PASSWORD
|
||||||
|
java -jar misael.jar --spring.datasource.url=$MISAEL_DATABASE --spring.datasource.password=$MISAEL_PASSWORD
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
from .models import *
|
|
||||||
|
|
||||||
admin.site.register(Oeuvre)
|
|
||||||
admin.site.register(JeuVideo)
|
|
||||||
admin.site.register(Film)
|
|
||||||
admin.site.register(Serie)
|
|
||||||
admin.site.register(Livre)
|
|
||||||
admin.site.register(Avis)
|
|
||||||
admin.site.register(Todo)
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ListsConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'lists'
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Generated by Django 5.0.1 on 2024-01-16 01:18
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='JeuVideo',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200)),
|
|
||||||
('sortie', models.DateTimeField(verbose_name='Date de publication')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
# Generated by Django 5.0.1 on 2024-01-16 13:25
|
|
||||||
|
|
||||||
import django.db.models.deletion
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('lists', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Oeuvre',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=200)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='jeuvideo',
|
|
||||||
name='id',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='jeuvideo',
|
|
||||||
name='name',
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Film',
|
|
||||||
fields=[
|
|
||||||
('oeuvre_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='lists.oeuvre')),
|
|
||||||
('sortie', models.DateTimeField(verbose_name='Date de publication')),
|
|
||||||
],
|
|
||||||
bases=('lists.oeuvre',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Livre',
|
|
||||||
fields=[
|
|
||||||
('oeuvre_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='lists.oeuvre')),
|
|
||||||
('sortie', models.DateTimeField(verbose_name='Date de publication')),
|
|
||||||
],
|
|
||||||
bases=('lists.oeuvre',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Serie',
|
|
||||||
fields=[
|
|
||||||
('oeuvre_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='lists.oeuvre')),
|
|
||||||
('sortie', models.DateTimeField(verbose_name='Date de publication')),
|
|
||||||
],
|
|
||||||
bases=('lists.oeuvre',),
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Avis',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('texte', models.CharField()),
|
|
||||||
('oeuvre', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lists.oeuvre')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='jeuvideo',
|
|
||||||
name='oeuvre_ptr',
|
|
||||||
field=models.OneToOneField(auto_created=True, default=0, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='lists.oeuvre'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Todo',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('rank', models.IntegerField()),
|
|
||||||
('oeuvre', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='lists.oeuvre')),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
# Generated by Django 5.0.1 on 2024-01-16 15:27
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('lists', '0002_oeuvre_remove_jeuvideo_id_remove_jeuvideo_name_film_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='film',
|
|
||||||
name='sortie',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='jeuvideo',
|
|
||||||
name='sortie',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='livre',
|
|
||||||
name='sortie',
|
|
||||||
),
|
|
||||||
migrations.RemoveField(
|
|
||||||
model_name='serie',
|
|
||||||
name='sortie',
|
|
||||||
),
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='oeuvre',
|
|
||||||
name='sortie',
|
|
||||||
field=models.IntegerField(default=0, verbose_name='Année de sortie'),
|
|
||||||
preserve_default=False,
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='avis',
|
|
||||||
name='texte',
|
|
||||||
field=models.TextField(),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='oeuvre',
|
|
||||||
name='name',
|
|
||||||
field=models.CharField(max_length=200, verbose_name='Nom'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
|
|
||||||
class Oeuvre(models.Model):
|
|
||||||
name = models.CharField("Nom",max_length=200)
|
|
||||||
sortie = models.IntegerField("Année de sortie")
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.name + (" (" + str(self.sortie) + ")" if self.sortie else "")
|
|
||||||
|
|
||||||
class JeuVideo(Oeuvre):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Film(Oeuvre):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Serie(Oeuvre):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Livre(Oeuvre):
|
|
||||||
pass
|
|
||||||
|
|
||||||
class Avis(models.Model):
|
|
||||||
oeuvre = models.ForeignKey(Oeuvre, on_delete=models.CASCADE)
|
|
||||||
texte = models.TextField()
|
|
||||||
|
|
||||||
class Todo(models.Model):
|
|
||||||
oeuvre = models.ForeignKey(Oeuvre, on_delete=models.CASCADE)
|
|
||||||
rank = models.IntegerField()
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
from django.urls import path
|
|
||||||
|
|
||||||
from . import views
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("", views.index, name="index"),
|
|
||||||
]
|
|
||||||
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from django.shortcuts import render
|
|
||||||
from django.http import HttpResponse
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
|
||||||
return HttpResponse("Hello, world. You're at the polls index.")
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Django's command-line utility for administrative tasks."""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run administrative tasks."""
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'misael.settings')
|
|
||||||
try:
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ImportError(
|
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
|
||||||
"forget to activate a virtual environment?"
|
|
||||||
) from exc
|
|
||||||
execute_from_command_line(sys.argv)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
ASGI config for misael project.
|
|
||||||
|
|
||||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/5.0/howto/deployment/asgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'misael.settings')
|
|
||||||
|
|
||||||
application = get_asgi_application()
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for misael project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 5.0.1.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/5.0/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/5.0/ref/settings/
|
|
||||||
"""
|
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'django-insecure-xww0xj_dh_2xtex&q%y6zlsa_$anz&4ijs(94skyhu_ijt43m@'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = True
|
|
||||||
|
|
||||||
ALLOWED_HOSTS = []
|
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'lists.apps.ListsConfig',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'misael.urls'
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'misael.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
|
||||||
'NAME': 'misael',
|
|
||||||
'USER': 'misael',
|
|
||||||
'PASSWORD': 'UCmRhUBXS6HX9NeUs15oKxLZAZvixE7N+ONLHWRk1ag=',
|
|
||||||
'HOST': 'localhost',
|
|
||||||
'PORT': '5432',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/5.0/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'en-us'
|
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Paris'
|
|
||||||
|
|
||||||
USE_I18N = True
|
|
||||||
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/5.0/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
"""
|
|
||||||
URL configuration for misael project.
|
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/5.0/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.urls import include, path
|
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import include,path
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path("lists/", include("lists.urls")),
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
]
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for misael project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'misael.settings')
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
plugins {
|
||||||
|
id 'de.fayard.refreshVersions' version '0.60.5'
|
||||||
|
}
|
||||||
|
rootProject.name = 'misael'
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.bernard.misael;
|
||||||
|
|
||||||
|
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.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import com.bernard.misael.model.Privilege;
|
||||||
|
import com.bernard.misael.model.Role;
|
||||||
|
import com.bernard.misael.model.User;
|
||||||
|
import com.bernard.misael.repository.UserRepository;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class CustomUserDetailsService implements UserDetailsService {
|
||||||
|
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
public CustomUserDetailsService(UserRepository userRepository) {
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String pseudo) throws UsernameNotFoundException {
|
||||||
|
User user = userRepository.findByName(pseudo);
|
||||||
|
|
||||||
|
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(),
|
||||||
|
user.getPassword(),
|
||||||
|
Stream.concat(inducedPrivileges, roles).toList()
|
||||||
|
);
|
||||||
|
}else{
|
||||||
|
throw new UsernameNotFoundException("Invalid username or password.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.bernard.misael;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class MisaelApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(MisaelApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.bernard.misael;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
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(ResourceHandlerRegistry registry) {
|
||||||
|
|
||||||
|
String imgFolder = System.getenv("THECREW_IMAGES_FOLDER");
|
||||||
|
registry.addResourceHandler("/thecrew/**")
|
||||||
|
.addResourceLocations("file://"+imgFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.bernard.misael;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.thymeleaf.extras.springsecurity6.dialect.SpringSecurityDialect;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ThymeleafConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SpringSecurityDialect springSecurityDialect(){
|
||||||
|
return new SpringSecurityDialect();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package com.bernard.misael.model;
|
||||||
|
|
||||||
|
import com.bernard.misael.service.JsonNodeConverter;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Convert;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
@Table(name="answers")
|
||||||
|
public class Answer {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||||
|
@Column(name="id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "question",nullable = false)
|
||||||
|
private Question question;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "form",nullable = false)
|
||||||
|
private QuizzForm form;
|
||||||
|
|
||||||
|
@Convert(converter = JsonNodeConverter.class)
|
||||||
|
private JsonNode value;
|
||||||
|
|
||||||
|
public Answer(QuizzForm qf, Question q) {
|
||||||
|
super();
|
||||||
|
this.setQuestion(q);
|
||||||
|
this.setForm(qf);
|
||||||
|
this.setValue(JsonNodeFactory.instance.objectNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.bernard.misael.model;
|
||||||
|
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
|
||||||
|
public enum Privilege implements GrantedAuthority {
|
||||||
|
|
||||||
|
LIST_USERS,ADD_USERS,LIST_QUIZZ,CREATE_QUIZZ,VIEW_ALL_FORMS;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthority() {
|
||||||
|
return this.name();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.bernard.misael.model;
|
||||||
|
|
||||||
|
import com.bernard.misael.questions.QTypes;
|
||||||
|
import com.bernard.misael.questions.QuestionType;
|
||||||
|
import com.bernard.misael.service.JsonNodeConverter;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Entity
|
||||||
|
@Table(name="questions")
|
||||||
|
public class Question {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy=GenerationType.IDENTITY)
|
||||||
|
@Column(name="id")
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
@Setter
|
||||||
|
QTypes type;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
@Setter
|
||||||
|
private int index;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "quizz",nullable = false)
|
||||||
|
@Setter
|
||||||
|
private Quizz quizz;
|
||||||
|
|
||||||
|
@Setter
|
||||||
|
@Convert(converter = JsonNodeConverter.class)
|
||||||
|
private JsonNode value;
|
||||||
|
|
||||||
|
transient QuestionType qtype = null;
|
||||||
|
public QuestionType getQT() {
|
||||||
|
if(qtype==null){
|
||||||
|
qtype = type.construct(value);
|
||||||
|
}
|
||||||
|
return qtype;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.bernard.misael.model;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.ColumnDefault;
|
||||||
|
|
||||||
|
import jakarta.persistence.*;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
@Table(name="quizz")
|
||||||
|
public class Quizz {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable=false, unique=true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name="owner")
|
||||||
|
private User owner;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
@ColumnDefault("false")
|
||||||
|
private boolean isComplete;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
@ColumnDefault("0")
|
||||||
|
private int questionCount;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy="quizz")
|
||||||
|
private Set<Question> questions;
|
||||||
|
|
||||||
|
@Column(nullable=true)
|
||||||
|
private Integer publicQuestionCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.bernard.misael.model;
|
||||||
|
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Entity
|
||||||
|
@Table(name="quizzf")
|
||||||
|
public class QuizzForm {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "answerer",nullable = false)
|
||||||
|
private User user;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "quizz",nullable=false)
|
||||||
|
private Quizz quizz;
|
||||||
|
|
||||||
|
private boolean done;
|
||||||
|
private int currentQuestion;
|
||||||
|
private int answerStep;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
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,54 @@
|
|||||||
|
package com.bernard.misael.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import jakarta.persistence.CascadeType;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.FetchType;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.JoinTable;
|
||||||
|
import jakarta.persistence.ManyToMany;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@Entity
|
||||||
|
@Table(name="users")
|
||||||
|
public class User
|
||||||
|
{
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(nullable=false,unique = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@Column(nullable=false)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@ManyToMany(fetch = FetchType.EAGER, cascade=CascadeType.ALL)
|
||||||
|
@JoinTable(
|
||||||
|
name="users_roles",
|
||||||
|
joinColumns={@JoinColumn(name="USER_ID", referencedColumnName="ID")},
|
||||||
|
inverseJoinColumns={@JoinColumn(name="ROLE_ID", referencedColumnName="ID")})
|
||||||
|
private List<Role> roles = new ArrayList<>();
|
||||||
|
|
||||||
|
@OneToMany(mappedBy="owner")
|
||||||
|
private Set<Quizz> myQuizzs;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.bernard.misael.questions;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
import java.util.Spliterator;
|
||||||
|
import java.util.Spliterators;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.bernard.misael.service.exception.MalformedAnswerException;
|
||||||
|
import com.bernard.misael.service.exception.MalformedClientAnswerException;
|
||||||
|
import com.bernard.misael.service.exception.QuestionTypeException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
public class DccQuestion implements QuestionType{
|
||||||
|
|
||||||
|
String text;
|
||||||
|
String goodAnswer;
|
||||||
|
String[] wrongAnswers;
|
||||||
|
|
||||||
|
public DccQuestion(JsonNode data){
|
||||||
|
text = data.get("text").asText();
|
||||||
|
goodAnswer = data.get("good").asText();
|
||||||
|
JsonNode wrong = data.get("wrong");
|
||||||
|
wrongAnswers = new String[wrong.size()];
|
||||||
|
for(int i = 0; i<wrong.size();i++)
|
||||||
|
wrongAnswers[i] = wrong.get(i).asText();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode clientQuestionData(int step, JsonNode answer) throws QuestionTypeException{
|
||||||
|
ObjectNode o = JsonNodeFactory.instance.objectNode();
|
||||||
|
o.put("text", this.text);
|
||||||
|
switch (step) {
|
||||||
|
case 0:
|
||||||
|
return o;
|
||||||
|
case 1:
|
||||||
|
Random r = new Random();
|
||||||
|
if(!answer.has("type") || !answer.get("type").isInt()) throw new MalformedAnswerException();
|
||||||
|
int k = answer.get("type").asInt();
|
||||||
|
switch(k) {
|
||||||
|
case 0:
|
||||||
|
return o;
|
||||||
|
case 2:
|
||||||
|
case 4:
|
||||||
|
List<String> answers = new ArrayList<>(k);
|
||||||
|
answers.add(goodAnswer);
|
||||||
|
for(int i = 0;i<k-1;i++)
|
||||||
|
answers.add(wrongAnswers[i]);
|
||||||
|
Collections.shuffle(answers, r);
|
||||||
|
o.putArray("answers").addAll(answers.stream().map(JsonNodeFactory.instance::textNode).collect(Collectors.toList()));
|
||||||
|
return o;
|
||||||
|
default:
|
||||||
|
throw new MalformedAnswerException();
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Question step invalid : "+step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer) throws QuestionTypeException {
|
||||||
|
ObjectNode o = JsonNodeFactory.instance.objectNode();
|
||||||
|
switch (step) {
|
||||||
|
case 0:
|
||||||
|
if(!clientAnswer.isInt()) throw new MalformedClientAnswerException();
|
||||||
|
int answerType = clientAnswer.asInt();
|
||||||
|
if(answerType != 0 && answerType != 2 && answerType != 4)
|
||||||
|
throw new IllegalArgumentException("Illegal answer type: "+answerType);
|
||||||
|
o.put("type", answerType);
|
||||||
|
return new AnswerResult(o, false, 1);
|
||||||
|
case 1:
|
||||||
|
if(!clientAnswer.isTextual()) throw new MalformedClientAnswerException();
|
||||||
|
String answer = clientAnswer.asText();
|
||||||
|
o = oldAnswer.deepCopy();
|
||||||
|
o.put("answer", answer);
|
||||||
|
return new AnswerResult(o, true, -1);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Question step invalid : "+step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Logger LOG = LoggerFactory.getLogger(DccQuestion.class);
|
||||||
|
|
||||||
|
public static boolean validate(JsonNode data) {
|
||||||
|
LOG.info(data.toPrettyString());
|
||||||
|
LOG.info(Stream.of(data.get("wrong")).map(p -> p.isTextual()+"->"+p).collect(Collectors.joining(";")));
|
||||||
|
return data.isObject() &&
|
||||||
|
data.has("text") && data.get("text").isTextual() &&
|
||||||
|
data.has("good") && data.get("good").isTextual() &&
|
||||||
|
data.has("wrong") && data.get("wrong").isArray() &&
|
||||||
|
data.get("wrong").size() >= 3 &&
|
||||||
|
StreamSupport.stream(Spliterators.spliterator(data.get("wrong").elements(),data.get("wrong").size(),Spliterator.SIZED),false).allMatch(p ->
|
||||||
|
p.isTextual()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final JsonNode getDefaultDccQuestion() {
|
||||||
|
ObjectNode q = JsonNodeFactory.instance.objectNode();
|
||||||
|
q.set("text",JsonNodeFactory.instance.textNode(""));
|
||||||
|
q.set("good",JsonNodeFactory.instance.textNode(""));
|
||||||
|
ArrayNode wrong = JsonNodeFactory.instance.arrayNode(3);
|
||||||
|
for(int i=0;i<3;i++)wrong.add(JsonNodeFactory.instance.textNode(""));
|
||||||
|
q.set("wrong",wrong);
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package com.bernard.misael.questions;
|
||||||
|
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum QTypes {
|
||||||
|
|
||||||
|
DCC(1,DccQuestion.class,DccQuestion::new,DccQuestion::validate,DccQuestion.getDefaultDccQuestion());
|
||||||
|
|
||||||
|
private int id;
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private Class<? extends QuestionType> type;
|
||||||
|
private Function<JsonNode,QuestionType> constructor;
|
||||||
|
private Function<JsonNode,Boolean> validator;
|
||||||
|
@Getter
|
||||||
|
private JsonNode defaultQuestion;
|
||||||
|
|
||||||
|
public static QTypes findById(final int id) {
|
||||||
|
for(QTypes value : QTypes.values())
|
||||||
|
if (value.id == id)
|
||||||
|
return value;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public QuestionType construct(JsonNode data){
|
||||||
|
return this.constructor.apply(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean validate(JsonNode data){
|
||||||
|
return this.validator.apply(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.bernard.misael.questions;
|
||||||
|
|
||||||
|
import com.bernard.misael.service.exception.QuestionTypeException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
public interface QuestionType {
|
||||||
|
|
||||||
|
JsonNode clientQuestionData(int step, JsonNode answer) throws QuestionTypeException;
|
||||||
|
|
||||||
|
AnswerResult clientAnswers(int step, JsonNode oldAnswer, JsonNode clientAnswer) throws QuestionTypeException;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public static class AnswerResult {
|
||||||
|
private JsonNode newAnswer;
|
||||||
|
private boolean nextQuestion;
|
||||||
|
private int nextStep;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.bernard.misael.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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);
|
||||||
|
public List<Answer> findByFormAndQuestionIn(QuizzForm qf, List<Question> qz);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.bernard.misael.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import com.bernard.misael.model.Question;
|
||||||
|
import com.bernard.misael.model.Quizz;
|
||||||
|
|
||||||
|
|
||||||
|
public interface QuestionRepository extends JpaRepository<Question,Long> {
|
||||||
|
|
||||||
|
public Question findByQuizzAndIndex(Quizz quizz, int index);
|
||||||
|
public Set<Question> findByQuizz(Quizz quizz);
|
||||||
|
public List<Question> findByQuizzOrderByIndexAsc(Quizz quizz);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.bernard.misael.repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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 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.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;
|
||||||
|
import com.bernard.misael.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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package com.bernard.misael.repository;
|
||||||
|
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import com.bernard.misael.model.User;
|
||||||
|
|
||||||
|
public interface UserRepository extends JpaRepository<User, Long> {
|
||||||
|
|
||||||
|
User findByName(String pseudo);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package com.bernard.misael.service;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.context.event.ContextRefreshedEvent;
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.bernard.misael.model.Privilege;
|
||||||
|
import com.bernard.misael.model.Role;
|
||||||
|
import com.bernard.misael.model.User;
|
||||||
|
import com.bernard.misael.repository.RoleRepository;
|
||||||
|
import com.bernard.misael.repository.UserRepository;
|
||||||
|
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class AdminMaker implements
|
||||||
|
ApplicationListener<ContextRefreshedEvent> {
|
||||||
|
|
||||||
|
boolean alreadySetup = false;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserRepository userRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RoleRepository roleRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private PasswordEncoder passwordEncoder;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void onApplicationEvent(@NonNull ContextRefreshedEvent event) {
|
||||||
|
|
||||||
|
if (alreadySetup)
|
||||||
|
return;
|
||||||
|
Logger log = Logger.getLogger("AdminMaker");
|
||||||
|
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");
|
||||||
|
if (mysaa == null) {
|
||||||
|
User user = new User();
|
||||||
|
user.setName("mysaa");
|
||||||
|
user.setPassword(passwordEncoder.encode("super"));
|
||||||
|
user.setRoles(Arrays.asList(adminRole));
|
||||||
|
userRepository.save(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
alreadySetup = true;
|
||||||
|
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,32 @@
|
|||||||
|
package com.bernard.misael.service;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import jakarta.persistence.AttributeConverter;
|
||||||
|
|
||||||
|
|
||||||
|
public class JsonNodeConverter implements AttributeConverter<JsonNode, String> {
|
||||||
|
|
||||||
|
private ObjectMapper om;
|
||||||
|
|
||||||
|
public JsonNodeConverter() {
|
||||||
|
this.om = new ObjectMapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String convertToDatabaseColumn(JsonNode attribute) {
|
||||||
|
return attribute.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode convertToEntityAttribute(String dbData) {
|
||||||
|
try {
|
||||||
|
return om.readTree(dbData);
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.bernard.misael.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
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.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 boolean canAccessQuizz(User user, long quizzId);
|
||||||
|
public List<Quizz> editableQuizz(User user);
|
||||||
|
public List<Quizz> answerableQuizz(User user);
|
||||||
|
|
||||||
|
public boolean canEditQuizz(User user, long quizzId);
|
||||||
|
public Optional<QuizzForm> canViewQuizzForm(User user, long quizzFormId);
|
||||||
|
public Optional<Quizz> canViewQuizzFormsOfQuizz(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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,601 @@
|
|||||||
|
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.Privilege;
|
||||||
|
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.*;
|
||||||
|
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.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.persistence.EntityNotFoundException;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class QuizzManagerImpl implements QuizzManager {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserRepository uRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
QuizzFormRepository qfRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
QuizzRepository qRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
QuestionRepository questionRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
AnswerRepository answerRepository;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
UserService uService;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@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);
|
||||||
|
if(!oquizz.get().isComplete())
|
||||||
|
return errorNode("Quizz is not complete");
|
||||||
|
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+")");
|
||||||
|
if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE))
|
||||||
|
return errorNode("La question suivante est encore bloquée");
|
||||||
|
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);
|
||||||
|
if(!oquizz.get().isComplete())
|
||||||
|
return errorNode("Quizz is not complete");
|
||||||
|
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();
|
||||||
|
if(qindex >= Optional.ofNullable(quizz.getPublicQuestionCount()).orElse(Integer.MAX_VALUE))
|
||||||
|
return errorNode("La question suivante est encore bloquée");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Quizz newQuizz(User user) {
|
||||||
|
Quizz q = new Quizz();
|
||||||
|
q.setName("Super questions de "+user.getName()+" ("+Integer.toHexString((int)(Math.random()*0xFFFFFFF))+")");
|
||||||
|
q.setOwner(user);
|
||||||
|
q = qRepository.save(q);
|
||||||
|
return q;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final JsonNode errorNode(String err){
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(false));
|
||||||
|
out.set("message", JsonNodeFactory.instance.textNode(err));
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean canAccessQuizz(User user, long quizzId) {
|
||||||
|
try{
|
||||||
|
Quizz quizz = qRepository.getReferenceById(quizzId);
|
||||||
|
return quizz.getPublicQuestionCount()!=null || quizz.getOwner().equals(user);
|
||||||
|
} catch (EntityNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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()))
|
||||||
|
).collect(Collectors.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()))
|
||||||
|
.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));
|
||||||
|
if(oquizz.get().isComplete())
|
||||||
|
return Optional.of(errorNode("Quizz is complete, cannot edit, answers might have already been cast"));
|
||||||
|
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()));
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
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) {
|
||||||
|
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);
|
||||||
|
|
||||||
|
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);
|
||||||
|
} else {
|
||||||
|
n =q.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
out.set("success", JsonNodeFactory.instance.booleanNode(true));
|
||||||
|
out.set("new", n);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<QuizzForm> canViewQuizzForm(User user, long quizzFormId) {
|
||||||
|
Optional<QuizzForm> oqf = qfRepository.findById(quizzFormId);
|
||||||
|
if(oqf.isEmpty()) return oqf;
|
||||||
|
QuizzForm qf = oqf.get();
|
||||||
|
if(!qf.isDone()) return Optional.empty();
|
||||||
|
if(qf.getUser().equals(user)) return oqf;
|
||||||
|
if(uService.hasPrivilege(user, Privilege.VIEW_ALL_FORMS)) return oqf;
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<Quizz> canViewQuizzFormsOfQuizz(User user, long quizzId) {
|
||||||
|
Optional<Quizz> oq = qRepository.findById(quizzId);
|
||||||
|
if(oq.isEmpty()) return oq;
|
||||||
|
Quizz q = oq.get();
|
||||||
|
if(q.getOwner().equals(user)) return oq;
|
||||||
|
if(uService.hasPrivilege(user, Privilege.VIEW_ALL_FORMS)) return oq;
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JsonNode getQuizzFormData(User user, long quizzFormId) {
|
||||||
|
Optional<QuizzForm> oqf = canViewQuizzForm(user, quizzFormId);
|
||||||
|
if(oqf.isEmpty())
|
||||||
|
return errorNode("Could not access the quizzform"); //TODO more precise error node
|
||||||
|
QuizzForm form = oqf.get();
|
||||||
|
List<Question> questions = questionRepository.findByQuizzOrderByIndexAsc(form.getQuizz());
|
||||||
|
List<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) {
|
||||||
|
Optional<Quizz> oq = canViewQuizzFormsOfQuizz(u, quizzId);
|
||||||
|
if(oq.isEmpty())
|
||||||
|
return errorNode("Could not access the quizzform"); //TODO more precise error node
|
||||||
|
Quizz quizz = oq.get();
|
||||||
|
ObjectNode out = JsonNodeFactory.instance.objectNode();
|
||||||
|
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.valueOf(p.getIndex()).compareTo(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);
|
||||||
|
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) {
|
||||||
|
Optional<Quizz> oq = canViewQuizzFormsOfQuizz(user, quizzId);
|
||||||
|
if(oq.isEmpty())
|
||||||
|
return errorNode("Could not access the forms for this quizz"); //TODO more precise error node
|
||||||
|
Quizz quizz = oq.get();
|
||||||
|
List<QuizzForm> quizzForms = qfRepository.findByQuizz(quizz);
|
||||||
|
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("data", formsNode);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Quizz duplicateQuizz(User u, long quizzId) {
|
||||||
|
if (!canEditQuizz(u, quizzId))
|
||||||
|
return null;
|
||||||
|
Optional<Quizz> oq = qRepository.findById(quizzId);
|
||||||
|
// CHECKED BEFORE if (oq.isEmpty()) return null;
|
||||||
|
Quizz q = oq.get();
|
||||||
|
|
||||||
|
|
||||||
|
Quizz nq = new Quizz();
|
||||||
|
nq.setName(q.getName() + "("+ Integer.toHexString((int)(Math.random()*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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.bernard.misael.service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.bernard.misael.model.Privilege;
|
||||||
|
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();
|
||||||
|
|
||||||
|
boolean hasPrivilege(User u, Privilege p);
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
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.Privilege;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasPrivilege(User u, Privilege p) {
|
||||||
|
//TODO faire une query sql propre avec ça
|
||||||
|
return u.getRoles().stream().anyMatch(r -> r.getPrivileges().contains(p));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.bernard.misael.service.dto;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserDto
|
||||||
|
{
|
||||||
|
private Long id;
|
||||||
|
@NotEmpty(message = "User name should not be empty")
|
||||||
|
private String name;
|
||||||
|
@NotEmpty(message = "Password should not be empty")
|
||||||
|
private String password;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.bernard.misael.service.exception;
|
||||||
|
|
||||||
|
public class MalformedAnswerException extends QuestionTypeException{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.bernard.misael.service.exception;
|
||||||
|
|
||||||
|
public class MalformedClientAnswerException extends QuestionTypeException {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package com.bernard.misael.service.exception;
|
||||||
|
|
||||||
|
public abstract class QuestionTypeException extends Exception{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
package com.bernard.misael.web;
|
||||||
|
|
||||||
|
import java.security.Principal;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.security.access.annotation.Secured;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.validation.BindingResult;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import com.bernard.misael.model.Role;
|
||||||
|
import com.bernard.misael.model.User;
|
||||||
|
import com.bernard.misael.repository.UserRepository;
|
||||||
|
import com.bernard.misael.service.UserService;
|
||||||
|
import com.bernard.misael.service.dto.UserDto;
|
||||||
|
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class AuthController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserService userService;
|
||||||
|
@Autowired
|
||||||
|
private UserRepository urepo;
|
||||||
|
|
||||||
|
public AuthController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
public String loginPage() {
|
||||||
|
return "login";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/")
|
||||||
|
public String index(Model model) {
|
||||||
|
return "index";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/users")
|
||||||
|
@Secured("LIST_USERS")
|
||||||
|
public String listUsers(Model model, Principal p){
|
||||||
|
// User should have MANAGE_USERS now
|
||||||
|
UserDto newUser = new UserDto();
|
||||||
|
model.addAttribute("newuser",newUser);
|
||||||
|
List<UserInfo> userz = urepo.findAll().stream().map(UserInfo::new).sorted().toList();
|
||||||
|
model.addAttribute("users", userz);
|
||||||
|
return "users";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/adduser")
|
||||||
|
@Secured("ADD_USERS")
|
||||||
|
public String registration(@Valid @ModelAttribute("newuser") UserDto userDto,
|
||||||
|
BindingResult result,
|
||||||
|
Model model,
|
||||||
|
Principal p){
|
||||||
|
User existingUser = userService.findUserByName(userDto.getName());
|
||||||
|
|
||||||
|
if(existingUser != null){
|
||||||
|
result.reject("User is already registered");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(result.hasErrors()){
|
||||||
|
List<UserInfo> userz = urepo.findAll().stream().map(UserInfo::new).sorted().toList();
|
||||||
|
model.addAttribute("users", userz);
|
||||||
|
model.addAttribute("newuser",userDto);
|
||||||
|
return "redirect:/users?duplicate";
|
||||||
|
}
|
||||||
|
userService.saveUser(userDto);
|
||||||
|
return "redirect:/users?success";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/change-password")
|
||||||
|
public String showChangePassword(){
|
||||||
|
return "change-password";
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/change-password/change")
|
||||||
|
public String changePassword(@RequestParam("new-password") String newPassword, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
u = userService.findUserByName(p.getName());
|
||||||
|
userService.changePassword(u, newPassword);
|
||||||
|
|
||||||
|
return "redirect:/change-password?success";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
public static class UserInfo implements Comparable<UserInfo> {
|
||||||
|
private long id;
|
||||||
|
private String pseudo;
|
||||||
|
private String roles;
|
||||||
|
public UserInfo(User u){
|
||||||
|
this.id = u.getId();
|
||||||
|
this.pseudo = u.getName();
|
||||||
|
this.roles = u.getRoles().stream().map(Role::getName).collect(Collectors.joining(";"));
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public int compareTo(UserInfo other) {
|
||||||
|
return this.pseudo.compareTo(other.pseudo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
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.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.model.Quizz;
|
||||||
|
import com.bernard.misael.model.QuizzForm;
|
||||||
|
import com.bernard.misael.model.User;
|
||||||
|
import com.bernard.misael.questions.QTypes;
|
||||||
|
import com.bernard.misael.repository.QuizzFormRepository;
|
||||||
|
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;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
QuizzFormRepository qfrepo;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List all quizz
|
||||||
|
*/
|
||||||
|
@GetMapping("/quizz")
|
||||||
|
public String getQuizz(Model model, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u!=null) {
|
||||||
|
model.addAttribute("answerableQuizz",qm.answerableQuizz(u));
|
||||||
|
model.addAttribute("editableQuizz",qm.editableQuizz(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
return "quizz.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* List all forms started or finished by the user
|
||||||
|
*/
|
||||||
|
@GetMapping("/forms")
|
||||||
|
public String getForms(Model model, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
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) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
Optional<QuizzForm> oqf = qm.canViewQuizzForm(u, id);
|
||||||
|
if (oqf.isEmpty())
|
||||||
|
//TODO Faire un mesasge d'erreur dépendant des circonstances (unatuhorized, not found, not complete ...)
|
||||||
|
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
|
||||||
|
|
||||||
|
m.addAttribute("formId", id);
|
||||||
|
return "showform.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Show one (completed) form of one user
|
||||||
|
*/
|
||||||
|
@GetMapping("/showformsadvancements/{id}")
|
||||||
|
public Object showFormsAdvancements(@PathVariable("id") long id, Model m, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
Optional<Quizz> oq = qm.canViewQuizzFormsOfQuizz(u, id);
|
||||||
|
if (oq.isEmpty())
|
||||||
|
//TODO Faire un mesasge d'erreur dépendant des circonstances (unatuhorized, not found, not complete ...)
|
||||||
|
return new ResponseEntity<>(JsonNodeFactory.instance.objectNode(),HttpStatus.UNAUTHORIZED);
|
||||||
|
|
||||||
|
m.addAttribute("quizzId", id);
|
||||||
|
return "showformadvancements.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form
|
||||||
|
*/
|
||||||
|
@PostMapping("/getformdata/{id}")
|
||||||
|
public Object showFormApi(@PathVariable("id") long id, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
JsonNode out = qm.getQuizzFormData(u, id);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the forms for a specific quizz
|
||||||
|
*/
|
||||||
|
@GetMapping("/getallformsdata/{id}")
|
||||||
|
public Object getAllFormsData(@PathVariable("id") long id, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
JsonNode out = qm.getAllFormsData(u, id);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form
|
||||||
|
*/
|
||||||
|
@PostMapping("/getformadvancements/{id}")
|
||||||
|
public Object showFormAdvancements(@PathVariable("id") long id, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
JsonNode out = qm.getQuizzFormAdvancments(u, id);
|
||||||
|
return new ResponseEntity<>(out, HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* API get the form
|
||||||
|
*/
|
||||||
|
@GetMapping("/duplicate-quizz/{id}")
|
||||||
|
public Object duplicateQuizz(@PathVariable("id") long id, Principal p) {
|
||||||
|
User u = null;
|
||||||
|
if (p!=null)
|
||||||
|
u = ur.findByName(p.getName());
|
||||||
|
if(u==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
Quizz q = qm.duplicateQuizz(u, id);
|
||||||
|
if(q == null)
|
||||||
|
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
|
||||||
|
return new ResponseEntity<>(HttpStatus.OK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/new-quizz")
|
||||||
|
@Secured("CREATE_QUIZZ")
|
||||||
|
public Object newQuizz(Principal p, Model m) {
|
||||||
|
if (p==null)
|
||||||
|
return "redirect:/login?restricted";
|
||||||
|
User u = ur.findByName(p.getName());
|
||||||
|
if (u==null)
|
||||||
|
return new ResponseEntity<>(HttpStatus.FORBIDDEN);
|
||||||
|
Quizz q = qm.newQuizz(u);
|
||||||
|
|
||||||
|
return "redirect:/questions/quizz-edit/"+Long.toString(q.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
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,36 @@
|
|||||||
|
package com.bernard.misael.web;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.springframework.core.io.InputStreamResource;
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:postgresql://127.0.0.1:5432/misael
|
||||||
|
username: misael
|
||||||
|
password: misael-dev
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
hikari:
|
||||||
|
schema: misael
|
||||||
|
flyway:
|
||||||
|
enabled: true
|
||||||
|
locations: classpath:db/migration/structure, classpath:db/migration/data
|
||||||
|
validate-on-migrate: true
|
||||||
|
default-schema: misael
|
||||||
|
create-schemas: true
|
||||||
|
session:
|
||||||
|
jdbc:
|
||||||
|
initialize-schema: always
|
||||||
|
security:
|
||||||
|
user:
|
||||||
|
name: mysaa
|
||||||
|
|
||||||
|
jpa:
|
||||||
|
properties:
|
||||||
|
javax:
|
||||||
|
persistence:
|
||||||
|
schema-generation:
|
||||||
|
create-source: metadata
|
||||||
|
scripts:
|
||||||
|
action: update
|
||||||
|
create-target: db-migration.sql
|
||||||
|
database:
|
||||||
|
action: none
|
||||||
|
hibernate:
|
||||||
|
ddl-auto: none
|
||||||
|
generate-ddl: true
|
||||||
|
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
root: INFO
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
create table roles (id bigint generated by default as identity, name varchar(255) not null, primary key (id));
|
||||||
|
create table users (id bigint generated by default as identity, name varchar(255) not null, password varchar(255) not null, primary key (id));
|
||||||
|
create table users_roles (user_id bigint not null, role_id bigint not null);
|
||||||
|
alter table if exists roles drop constraint if exists UKofx66keruapi6vyqpv6f2or37;
|
||||||
|
alter table if exists roles add constraint UKofx66keruapi6vyqpv6f2or37 unique (name);
|
||||||
|
alter table if exists users_roles add constraint FKj6m8fwv7oqv74fcehir1a9ffy foreign key (role_id) references roles;
|
||||||
|
alter table if exists users_roles add constraint FK2o0jvgh89lemvvo17cbqvdxaa foreign key (user_id) references users;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
create table answers (id bigint generated by default as identity, value varchar(255), form bigint not null, question bigint not null, primary key (id));
|
||||||
|
create table questions (id bigint generated by default as identity, index integer not null, type smallint not null check (type between 0 and 0), value varchar(255), quizz bigint not null, primary key (id));
|
||||||
|
create table quizz (id bigint generated by default as identity, is_public boolean default false not null, name varchar(255) not null, question_count integer default 0 not null, owner bigint, primary key (id));
|
||||||
|
create table quizzf (id bigint generated by default as identity, answer_step integer not null, current_question integer not null, done boolean not null, quizz bigint not null, answerer bigint not null, primary key (id));
|
||||||
|
alter table if exists quizz drop constraint if exists UKc1plspc0ecmwqqpfaf24avb4c;
|
||||||
|
alter table if exists quizz add constraint UKc1plspc0ecmwqqpfaf24avb4c unique (name);
|
||||||
|
alter table if exists answers add constraint FKjtutlsv5n10071rq261lcjovm foreign key (form) references quizzf;
|
||||||
|
alter table if exists answers add constraint FK54dobrdq2u51m4u8s7kg0as8v foreign key (question) references questions;
|
||||||
|
alter table if exists questions add constraint FKq12h25ynjok1m497gwos511te foreign key (quizz) references quizz;
|
||||||
|
alter table if exists quizz add constraint FKfeoogns8m4m4hvno1ttqb30wm foreign key (owner) references users;
|
||||||
|
alter table if exists quizzf add constraint FK4ukbg2yaa93gs5nx1s5d9rqu4 foreign key (quizz) references quizz;
|
||||||
|
alter table if exists quizzf add constraint FK7gvxodb5t2n68ot577ig9u3w6 foreign key (answerer) references users;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
create table role_privileges (id bigint not null, privileges varchar(255) not null check (privileges in ('LIST_USERS','ADD_USERS','LIST_QUIZZ')), primary key (id, privileges));
|
||||||
|
alter table if exists role_privileges add constraint FK7xfa4foqs58j7rhk6ex78hpf3 foreign key (id) references roles;
|
||||||
@@ -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,98 @@
|
|||||||
|
/* Button box */
|
||||||
|
div.buttonbox {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
textarea.squared-input {
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
margin-bottom: 12px;
|
||||||
|
width: 400px;
|
||||||
|
min-height: 65px;
|
||||||
|
}
|
||||||
|
/* Common to all the answer buttons*/
|
||||||
|
button.squared-button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #1F1A25;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
font-weight: 800;
|
||||||
|
text-transform: uppercase;
|
||||||
|
padding: 4px;
|
||||||
|
font-size: 1rem;
|
||||||
|
|
||||||
|
margin-bottom: 12px;
|
||||||
|
width: 400px;
|
||||||
|
min-height: 65px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.squared-button:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.squared-button:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.squared-button:active {
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*@import url("https://fonts.googleapis.com/css?family=Muli:400,700,800&display=swap");
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #1F1A25;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Color 1 */
|
||||||
|
button.squared-button.bcol1 {
|
||||||
|
background-color: #78E0DC;
|
||||||
|
box-shadow: 0 8px #2dc5bf;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol1:hover, button.squared-button.bcol1:focus {
|
||||||
|
background-color: #4fd6d1;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.squared-button.bcol1:active {
|
||||||
|
box-shadow: 0 3px #2dc5bf;
|
||||||
|
}
|
||||||
|
/* Color 2 */
|
||||||
|
button.squared-button.bcol2 {
|
||||||
|
background-color: #FDE74C;
|
||||||
|
box-shadow: 0 8px #d7bd02;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol2:hover, button.squared-button.bcol2:focus {
|
||||||
|
background-color: #fcde0d;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol2:active {
|
||||||
|
box-shadow: 0 3px #d7bd02;
|
||||||
|
}
|
||||||
|
/* Color 3 */
|
||||||
|
button.squared-button.bcol3 {
|
||||||
|
background-color: #B8B3E9;
|
||||||
|
box-shadow: 0 8px #867ec8;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol3:hover, button.squared-button.bcol3:focus {
|
||||||
|
background-color: #b0acd9;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol3:active {
|
||||||
|
box-shadow: 0 3px #867ec8;
|
||||||
|
}
|
||||||
|
/* Color 4 */
|
||||||
|
button.squared-button.bcol4 {
|
||||||
|
background-color: #4fd337;
|
||||||
|
box-shadow: 0 8px #339721;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol4:hover, button.squared-button.bcol4:focus {
|
||||||
|
background-color: #40be29;
|
||||||
|
}
|
||||||
|
button.squared-button.bcol4:active {
|
||||||
|
box-shadow: 0 3px #339721;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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 {
|
||||||
|
display: table;
|
||||||
|
border-spacing: 2px;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
|
ol#questions-list li {
|
||||||
|
width: 90%;
|
||||||
|
background-color: lemonchiffon;
|
||||||
|
border: 2pt solid;
|
||||||
|
display: table-row;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: stretch;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#questions-list li div.button-box {
|
||||||
|
display:table-cell;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#questions-list li div.button-box button{
|
||||||
|
width: 7em;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol#questions-list li div.content-box {
|
||||||
|
display:table-cell;
|
||||||
|
background-color: gainsboro;
|
||||||
|
color: black;
|
||||||
|
font-size: 12pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* DCC */
|
||||||
|
div.dcc-box h5, div.dcc-box h5 input{
|
||||||
|
font-size: 16pt;
|
||||||
|
font-weight: bold;
|
||||||
|
text-decoration: underline;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@200;300;400;500;600;700&display=swap');
|
||||||
|
*{
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: 'Poppins', sans-serif;
|
||||||
|
}
|
||||||
|
nav{
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 75px;
|
||||||
|
background: #2980b9;
|
||||||
|
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
nav .navbar{
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 100%;
|
||||||
|
max-width: 90%;
|
||||||
|
background: #2980b9;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
nav .navbar .logo a{
|
||||||
|
color: #fff;
|
||||||
|
font-size: 27px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
nav .navbar .menu{
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.navbar .menu li{
|
||||||
|
list-style: none;
|
||||||
|
margin: 0 15px;
|
||||||
|
}
|
||||||
|
.navbar .menu li a{
|
||||||
|
color: #fff;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
main{
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #96c7e8;
|
||||||
|
font-size: 24px;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-top: 115px;
|
||||||
|
}
|
||||||
|
.button a{
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
right: 20px;
|
||||||
|
color: #fff;
|
||||||
|
background: #2980b9;
|
||||||
|
padding: 7px 12px;;
|
||||||
|
font-size: 18px;
|
||||||
|
border-radius: 6px;
|
||||||
|
box-shadow: rgba(0, 0, 0, 0.15);
|
||||||
|
}
|
||||||
|
div.alert {
|
||||||
|
background: #FFDDEE;
|
||||||
|
font-size: 20px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.welcome{
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
table, th, td {
|
||||||
|
border: 1px solid black;
|
||||||
|
border-collapse: collapse;
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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 |
@@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<div th:if="${param.success}">
|
||||||
|
<div class="alert alert-success"> Votre mot de passe a été changé</div><br/>
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
role="form"
|
||||||
|
th:action="@{/change-password/change}">
|
||||||
|
<label for="new-password">Nouveau mot de passe :</label>
|
||||||
|
<input type="password" id="new-password" name="new-password"/>
|
||||||
|
<br/>
|
||||||
|
<input id="connect" type="submit" value="Changer de mot de passe"/>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</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/form.css}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<script th:src="@{/webjars/jquery/1.9.1/jquery.min.js}"></script>
|
||||||
|
<main>
|
||||||
|
<span id="question-text"></span>
|
||||||
|
<span id="question-counter">0/0</span>
|
||||||
|
<div id="answers">
|
||||||
|
<div class="buttonbox" id="answer-select">
|
||||||
|
<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 bcol3" id="answer-select-cash">Cash</button>
|
||||||
|
</div>
|
||||||
|
<div class="buttonbox" id="answer-cash">
|
||||||
|
<textarea class="squared-input" id="answer-cash-input" type="text"></textarea>
|
||||||
|
<button class="squared-button bcol4" id="answer-cash-button">Envoyer la réponse</button>
|
||||||
|
</div>
|
||||||
|
<div class="buttonbox" id="answer-duo">
|
||||||
|
<button class="squared-button bcol1" id="answer-duo-button-1"></button>
|
||||||
|
<button class="squared-button bcol2" id="answer-duo-button-2"></button>
|
||||||
|
</div>
|
||||||
|
<div class="buttonbox" id="answer-carre">
|
||||||
|
<button class="squared-button bcol1" id="answer-carre-button-1"></button>
|
||||||
|
<button class="squared-button bcol2" id="answer-carre-button-2"></button>
|
||||||
|
<button class="squared-button bcol3" id="answer-carre-button-3"></button>
|
||||||
|
<button class="squared-button bcol4" id="answer-carre-button-4"></button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span id="error-textbox" style="color: red"></span>
|
||||||
|
<button id="question-button">Poser la question</button>
|
||||||
|
<script th:inline="javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var qid = /*[[${formid}]]*/ -1;
|
||||||
|
var qlength = /*[[${quizzLength}]]*/ -1;
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
qindex = 0
|
||||||
|
qstep = 0
|
||||||
|
function error(txt) {
|
||||||
|
console.log(txt)
|
||||||
|
$("#error-textbox").text(txt)
|
||||||
|
}
|
||||||
|
function clearerror() {
|
||||||
|
$("#error-textbox").text("")
|
||||||
|
}
|
||||||
|
function next() {
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/question/"+qid,
|
||||||
|
type: "GET",
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
qindex=res["index"]
|
||||||
|
qstep=res["step"]
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
clearerror()
|
||||||
|
$("#question-counter").text((res["index"]+1)+"/"+qlength)
|
||||||
|
$("#question-text").text(res["data"]["text"])
|
||||||
|
if(qstep==0){
|
||||||
|
$("#answer-select").show()
|
||||||
|
$("#answer-duo").hide()
|
||||||
|
$("#answer-carre").hide()
|
||||||
|
$("#answer-cash").hide()
|
||||||
|
}else if(qstep==1){
|
||||||
|
awrs = res["data"]["answers"]
|
||||||
|
|
||||||
|
$("#answer-select").hide()
|
||||||
|
$("#answer-duo").hide()
|
||||||
|
$("#answer-carre").hide()
|
||||||
|
$("#answer-cash").hide()
|
||||||
|
if(typeof awrs === 'undefined'){
|
||||||
|
$("#answer-cash").show()
|
||||||
|
$("#answer-cash-input").val("")
|
||||||
|
} else if (awrs.length == 2) {
|
||||||
|
$("#answer-duo").show()
|
||||||
|
$("#answer-duo-button-1").text(res.data.answers[0])
|
||||||
|
$("#answer-duo-button-2").text(res.data.answers[1])
|
||||||
|
} else if (awrs.length == 4) {
|
||||||
|
$("#answer-carre").show()
|
||||||
|
$("#answer-carre-button-1").text(res.data.answers[0])
|
||||||
|
$("#answer-carre-button-2").text(res.data.answers[1])
|
||||||
|
$("#answer-carre-button-3").text(res.data.answers[2])
|
||||||
|
$("#answer-carre-button-4").text(res.data.answers[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function answer1(q) {
|
||||||
|
asw={
|
||||||
|
index: qindex,
|
||||||
|
step: 0,
|
||||||
|
data: q
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: "POST",
|
||||||
|
url: "/questions/answer/"+qid,
|
||||||
|
data: JSON.stringify(asw),
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(res["success"]){
|
||||||
|
clearerror()
|
||||||
|
next()
|
||||||
|
} else {
|
||||||
|
error(res["message"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function answer2(value) {
|
||||||
|
asw={
|
||||||
|
index: qindex,
|
||||||
|
step: 1,
|
||||||
|
data: value
|
||||||
|
}
|
||||||
|
$.ajax({
|
||||||
|
contentType: 'application/json',
|
||||||
|
type: "POST",
|
||||||
|
url: "/questions/answer/"+qid,
|
||||||
|
data: JSON.stringify(asw),
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(res["success"]){
|
||||||
|
clearerror()
|
||||||
|
$("#answer-select").hide()
|
||||||
|
$("#answer-duo").hide()
|
||||||
|
$("#answer-carre").hide()
|
||||||
|
$("#answer-cash").hide()
|
||||||
|
if(qindex+1 >= qlength) {
|
||||||
|
$("#question-text").text("Plus de questions !")
|
||||||
|
} else {
|
||||||
|
$("#question-text").text("Question suivante ...")
|
||||||
|
next()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isdone = false
|
||||||
|
error(res["message"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$("#answer-select").hide()
|
||||||
|
$("#answer-duo").hide()
|
||||||
|
$("#answer-carre").hide()
|
||||||
|
$("#answer-cash").hide()
|
||||||
|
$("#answer-select-duo").on('click',() => answer1(2))
|
||||||
|
$("#answer-select-carre").on('click',() => answer1(4))
|
||||||
|
$("#answer-select-cash").on('click',() => answer1(0))
|
||||||
|
$("#answer-cash-button").on('click',() => answer2($("#answer-cash-input").val()))
|
||||||
|
$("#answer-duo-button-1").on('click',() => answer2(awrs[0]))
|
||||||
|
$("#answer-duo-button-2").on('click',() => answer2(awrs[1]))
|
||||||
|
$("#answer-carre-button-1").on('click',() => answer2(awrs[0]))
|
||||||
|
$("#answer-carre-button-2").on('click',() => answer2(awrs[1]))
|
||||||
|
$("#answer-carre-button-3").on('click',() => answer2(awrs[2]))
|
||||||
|
$("#answer-carre-button-4").on('click',() => answer2(awrs[3]))
|
||||||
|
$("#question-button").on('click',next)
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<main>
|
||||||
|
<h3>Formulaires terminés</h3>
|
||||||
|
<ul>
|
||||||
|
<li th:if="${#lists.isEmpty(finishedForms)}">Vous n'avez fini aucun formulaire !</li>
|
||||||
|
<li th:each="qf : ${finishedForms}"><a th:href="@{/questions/showform/{id}(id=${qf.id})}">Quizz <span th:text="${qf.quizz.name}"/></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>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<nav>
|
||||||
|
<div class="navbar">
|
||||||
|
<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">
|
||||||
|
<li><a href="/questions/quizz">Quizz</a></li>
|
||||||
|
<li><a href="/questions/forms">Forms</a></li>
|
||||||
|
<li sec:authorize="isAnonymous()"><a href="/login">Log in</a></li>
|
||||||
|
<li sec:authorize="isAuthenticated()"><a href="/logout">Log out</div></a></li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Misael</title>
|
||||||
|
<link rel="stylesheet" th:href="@{/css/style.css}"/>
|
||||||
|
<!-- Fontawesome CDN Link -->
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<main>
|
||||||
|
Youhou ! (Y a rien ici)
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<div th:if="${param.error}">
|
||||||
|
<div class="alert alert-danger">Invalid Email or Password</div><br/>
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.logout}">
|
||||||
|
<div class="alert alert-success"> You have been logged out.</div><br/>
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.restricted}">
|
||||||
|
<div class="alert alert-success"> Vous devez vous connecter pour vous accéder à cette page</div><br/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
role="form"
|
||||||
|
th:action="@{/login}">
|
||||||
|
<label for="username">Pseudo :</label>
|
||||||
|
<input type="text" id="username" name="username"/>
|
||||||
|
<br/>
|
||||||
|
<label for="password">Mot de passe :</label>
|
||||||
|
<input type="password" id="password" name="password"/>
|
||||||
|
<br/>
|
||||||
|
<input id="connect" type="submit" value="Se connecter"/>
|
||||||
|
</form>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
<link rel="stylesheet" th:href="@{/css/quizz-edit.css}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<script th:src="@{/webjars/jquery/1.9.1/jquery.min.js}" type="text/javascript"></script>
|
||||||
|
<script th:src="@{/webjars/lodash/4.17.21/lodash.js}" type="text/javascript"></script>
|
||||||
|
<main>
|
||||||
|
<h1>Quizz
|
||||||
|
<span id="question-name"></span>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<span id="error-textbox" style="color: red"></span>
|
||||||
|
|
||||||
|
<script th:inline="javascript">
|
||||||
|
/*<![CDATA[*/
|
||||||
|
var quizzid = /*[[${quizzId}]]*/ -1;
|
||||||
|
/*]]>*/
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
function error(txt) {
|
||||||
|
console.log(txt)
|
||||||
|
$("#error-textbox").text(txt)
|
||||||
|
}
|
||||||
|
QTYPES = {
|
||||||
|
"DCC": {
|
||||||
|
"display" : makeAnswerBlockBCC,
|
||||||
|
"edit": makeEditBlockBCC,
|
||||||
|
"read": readEditBlockBCC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// question position -> question data
|
||||||
|
questions={}
|
||||||
|
// Map question id -> question position
|
||||||
|
qpositions={}
|
||||||
|
// the name of the quizz
|
||||||
|
quizzName = ""
|
||||||
|
|
||||||
|
editing = new Set()
|
||||||
|
|
||||||
|
function getdata() {
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/quizz-edit/"+quizzid+"/get",
|
||||||
|
type: "POST",
|
||||||
|
dataType: "json",
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
questions=res["questions"]
|
||||||
|
quizzName=res["name"]
|
||||||
|
$("#question-name").text(quizzName)
|
||||||
|
$("#edit-question-name").val(quizzName)
|
||||||
|
for(var i=0;i<questions.length;i++){
|
||||||
|
qpositions[questions[i]["id"]] = i
|
||||||
|
internalCreateQuestion(i)
|
||||||
|
}
|
||||||
|
$(`.reorder-button`).hide()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function internalCreateQuestion(i){
|
||||||
|
qu = questions[i]
|
||||||
|
qid = qu["id"]
|
||||||
|
newhtml = `
|
||||||
|
<li id="q-${qid}">
|
||||||
|
<div class="content-box" id="q-${qid}-content"/>
|
||||||
|
<div class="content-box" id="q-${qid}-edit"/>
|
||||||
|
<div class="button-box">
|
||||||
|
<button class="display-button display-button-${qid}" id="q-${qid}-button-edit">Edit</button>
|
||||||
|
<button class="display-button display-button-${qid}" id="q-${qid}-type-edit">Type</button>
|
||||||
|
<button class="display-button display-button-${qid}" id="q-${qid}-remove">Supprimer</button>
|
||||||
|
<button class="edit-button edit-button-${qid}" id="q-${qid}-validate-edit">Valider</button>
|
||||||
|
<button class="edit-button edit-button-${qid}" id="q-${qid}-cancel-edit">Annuler</button>
|
||||||
|
<button class="reorder-button" id="q-${qid}-reorder-uup">Uup</button>
|
||||||
|
<button class="reorder-button" id="q-${qid}-reorder-up">Up</button>
|
||||||
|
<button class="reorder-button" id="q-${qid}-reorder-down">Down</button>
|
||||||
|
<button class="reorder-button" id="q-${qid}-reorder-ddown">DDown</button>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
`
|
||||||
|
newdom = $('<li/>').html(newhtml).contents()
|
||||||
|
$("#questions-list").append(newdom)
|
||||||
|
const theqid = qid
|
||||||
|
$(`#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 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 ^^
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function edit(qid) {
|
||||||
|
editing.add(qid)
|
||||||
|
editMode(qid)
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitEdit(qid) {
|
||||||
|
const newvalue = readEditBlockBCC(qid)
|
||||||
|
lockEdit(qid)
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/quizz-edit/"+quizzid+"/edit-question/"+qid,
|
||||||
|
type: "POST",
|
||||||
|
data: JSON.stringify(newvalue),
|
||||||
|
dataType: "json",
|
||||||
|
contentType: 'application/json',
|
||||||
|
success: function(res) {
|
||||||
|
console.log(res)
|
||||||
|
if(!res["success"]) {
|
||||||
|
console.log(res["message"])
|
||||||
|
error(res["message"])
|
||||||
|
unlockEdit(qid)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i = qpositions[qid]
|
||||||
|
questions[i]["value"] = newvalue
|
||||||
|
newcontent = QTYPES[questions[i]["type"]]["display"](qid,questions[i]["value"])
|
||||||
|
$(`#q-${qid}-content`).replaceWith(newcontent)
|
||||||
|
|
||||||
|
unlockEdit(qid)
|
||||||
|
editing.delete(qid)
|
||||||
|
contentMode(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"])
|
||||||
|
$(`#q-${qid}-edit`).replaceWith(newedit)
|
||||||
|
editing.delete(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 makeEditBlockBCC(id,question) {
|
||||||
|
html = `
|
||||||
|
<div class="content-box dcc-box" id="q-${id}-edit">
|
||||||
|
<h5><input id="q-${id}-edit-question"
|
||||||
|
type="text" value="${_.escape(question["text"])}"/></h5>
|
||||||
|
<ol>
|
||||||
|
<li class="right">☑ <input id="q-${id}-edit-good"
|
||||||
|
type="text" value="${_.escape(question["good"])}"/></li>
|
||||||
|
<li class="wrong">☒ <input id="q-${id}-edit-wrong1"
|
||||||
|
type="text" value="${_.escape(question["wrong"][0])}"/></li>
|
||||||
|
<li class="wrong">☒ <input id="q-${id}-edit-wrong2"
|
||||||
|
type="text" value="${_.escape(question["wrong"][1])}"/></li>
|
||||||
|
<li class="wrong">☒ <input id="q-${id}-edit-wrong3"
|
||||||
|
type="text" value="${_.escape(question["wrong"][2])}"/></li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
out = $('<div/>').html(html).contents()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
function readEditBlockBCC(id) {
|
||||||
|
questionText = $(`#q-${id}-edit-question`).val()
|
||||||
|
goodText = $(`#q-${id}-edit-good`).val()
|
||||||
|
wrongText1 = $(`#q-${id}-edit-wrong1`).val()
|
||||||
|
wrongText2 = $(`#q-${id}-edit-wrong2`).val()
|
||||||
|
wrongText3 = $(`#q-${id}-edit-wrong3`).val()
|
||||||
|
return {
|
||||||
|
"text": questionText,
|
||||||
|
"good": goodText,
|
||||||
|
"wrong": [wrongText1,wrongText2,wrongText3]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function makeAnswerBlockBCC(id,question) {
|
||||||
|
html = `
|
||||||
|
<div class="content-box dcc-box" id="q-${id}-content">
|
||||||
|
<h5>${_.escape(question["text"])}</h5>
|
||||||
|
<ol>
|
||||||
|
<li class="right">☑ ${_.escape(question["good"])}</li>
|
||||||
|
<li class="wrong">☒ ${_.escape(question["wrong"][0])}</li>
|
||||||
|
<li class="wrong">☒ ${_.escape(question["wrong"][1])}</li>
|
||||||
|
<li class="wrong">☒ ${_.escape(question["wrong"][2])}</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
out = $('<div/>').html(html).contents()
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
getdata()
|
||||||
|
</script>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<main>
|
||||||
|
<h3>Quizz à répondre</h3>
|
||||||
|
<ul>
|
||||||
|
<li th:if="${#lists.isEmpty(answerableQuizz)}">Aucun quizz de disponible malheureusement :(</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 à éditer</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>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -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,81 @@
|
|||||||
|
<!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>
|
||||||
|
|
||||||
|
<button id="refresh-data">Refresh</button>
|
||||||
|
<table id="forms-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Position</th>
|
||||||
|
<th>Step</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=[]
|
||||||
|
|
||||||
|
function getdata() {
|
||||||
|
$('#refresh-data').prop('disabled',true)
|
||||||
|
$.ajax({
|
||||||
|
url: "/questions/getformadvancements/"+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"]
|
||||||
|
|
||||||
|
$('.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)
|
||||||
|
}
|
||||||
|
$('#refresh-data').prop('disabled',false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#refresh-data').on('click',getdata)
|
||||||
|
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,61 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" dir="ltr">
|
||||||
|
<head>
|
||||||
|
<div th:replace="~{html-head}"/>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div th:replace="~{header}"/>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<div th:if="${param.success}">
|
||||||
|
Utilisteurice ajouté·e avec succès !
|
||||||
|
</div>
|
||||||
|
<div th:if="${param.duplicate}">
|
||||||
|
Le nom d'utilisateur·ice est déjà utilisé
|
||||||
|
</div>
|
||||||
|
<form
|
||||||
|
method="post"
|
||||||
|
role="form"
|
||||||
|
th:action="@{/adduser}"
|
||||||
|
th:object="${newuser}"
|
||||||
|
>
|
||||||
|
<label for="name">Nom</label>
|
||||||
|
<input
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
placeholder="Le pseudo"
|
||||||
|
th:field="*{name}"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
<p th:errors = "*{name}" th:if="${#fields.hasErrors('name')}"></p>
|
||||||
|
|
||||||
|
<label for="password">Password</label>
|
||||||
|
<input
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
placeholder="Enter password"
|
||||||
|
th:field="*{password}"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
<p th:errors = "*{password}" class="text-danger"
|
||||||
|
th:if="${#fields.hasErrors('password')}"></p>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" type="submit">Register</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Pseudo</th>
|
||||||
|
<th>Roles</th>
|
||||||
|
</tr>
|
||||||
|
<tr th:each="u : ${users}">
|
||||||
|
<td th:text="${u.id}"></td>
|
||||||
|
<td th:text="${u.pseudo}"></td>
|
||||||
|
<td th:text="${u.roles}"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.bernard.misael;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
|
||||||
|
import com.bernard.misael.questions.QTypes;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
class MisaelApplicationTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void contextLoads() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void validateDefaultQuestions() {
|
||||||
|
for(QTypes qt : QTypes.values()){
|
||||||
|
assert qt.validate(qt.getDefaultQuestion());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
#### Dependencies and Plugin versions with their available updates.
|
||||||
|
#### Generated by `./gradlew refreshVersions` version 0.60.5
|
||||||
|
####
|
||||||
|
#### Don't manually edit or split the comments that start with four hashtags (####),
|
||||||
|
#### they will be overwritten by refreshVersions.
|
||||||
|
####
|
||||||
|
#### suppress inspection "SpellCheckingInspection" for whole file
|
||||||
|
#### suppress inspection "UnusedProperty" for whole file
|
||||||
|
|
||||||
|
version.com.fasterxml.jackson.core..jackson-databind=2.18.3
|
||||||
|
|
||||||
|
version.org.junit.platform..junit-platform-launcher=1.12.0
|
||||||
|
|
||||||
|
version.org.springframework..spring-jdbc=6.2.3
|
||||||
|
## # available=7.0.0-M1
|
||||||
|
## # available=7.0.0-M2
|
||||||
|
|
||||||
|
version.org.springframework.boot..spring-boot-docker-compose=3.4.3
|
||||||
|
|
||||||
|
version.org.webjars..jquery=1.9.1
|
||||||
|
## # available=1.10.1
|
||||||
|
## # available=1.10.2
|
||||||
|
## # available=1.10.2-1
|
||||||
|
## # available=1.11.0
|
||||||
|
## # available=1.11.0-1
|
||||||
|
## # available=1.11.1
|
||||||
|
## # available=1.11.2
|
||||||
|
## # available=1.12.0
|
||||||
|
## # available=1.12.1
|
||||||
|
## # available=1.12.2
|
||||||
|
## # available=1.12.3
|
||||||
|
## # available=1.12.4
|
||||||
|
## # available=2.0.0
|
||||||
|
## # available=2.0.2
|
||||||
|
## # available=2.0.3
|
||||||
|
## # available=2.0.3-1
|
||||||
|
## # available=2.1.0
|
||||||
|
## # available=2.1.0-1
|
||||||
|
## # available=2.1.0-2
|
||||||
|
## # available=2.1.0-3
|
||||||
|
## # available=2.1.1
|
||||||
|
## # available=2.1.3
|
||||||
|
## # available=2.2.0
|
||||||
|
## # available=2.2.1
|
||||||
|
## # available=2.2.2
|
||||||
|
## # available=2.2.3
|
||||||
|
## # available=2.2.4
|
||||||
|
## # available=3.0.0-alpha1
|
||||||
|
## # available=3.0.0-rc1
|
||||||
|
## # available=3.0.0
|
||||||
|
## # available=3.1.0
|
||||||
|
## # available=3.1.1
|
||||||
|
## # available=3.1.1-1
|
||||||
|
## # available=3.2.0
|
||||||
|
## # available=3.2.1
|
||||||
|
## # available=3.3.0
|
||||||
|
## # available=3.3.1
|
||||||
|
## # available=3.3.1-1
|
||||||
|
## # available=3.3.1-2
|
||||||
|
## # available=3.4.0
|
||||||
|
## # available=3.4.1
|
||||||
|
## # available=3.5.0
|
||||||
|
## # available=3.5.1
|
||||||
|
## # available=3.6.0
|
||||||
|
## # available=3.6.1
|
||||||
|
## # available=3.6.2
|
||||||
|
## # available=3.6.3
|
||||||
|
## # available=3.6.4
|
||||||
|
## # available=3.7.0
|
||||||
|
## # available=3.7.1
|
||||||
|
|
||||||
|
version.org.thymeleaf.extras..thymeleaf-extras-springsecurity6=3.1.3.RELEASE
|
||||||
|
|
||||||
|
version.org.projectlombok..lombok=1.18.36
|
||||||
|
|
||||||
|
version.org.postgresql..postgresql=42.7.5
|
||||||
|
|
||||||
|
version.org.flywaydb..flyway-database-postgresql=11.3.2
|
||||||
|
## # available=11.3.3
|
||||||
|
## # available=11.3.4
|
||||||
|
|
||||||
|
version.org.flywaydb..flyway-core=11.3.2
|
||||||
|
## # available=11.3.3
|
||||||
|
## # available=11.3.4
|
||||||
|
|
||||||
|
version.jakarta.validation..jakarta.validation-api=3.1.1
|
||||||
|
|
||||||
|
version.com.fasterxml.jackson.datatype..jackson-datatype-hibernate5=2.18.2
|
||||||
|
## # available=2.18.3
|
||||||
|
|
||||||
|
plugin.org.springframework.boot=3.4.2
|
||||||
|
## # available=3.4.3
|
||||||
|
|
||||||
|
plugin.org.flywaydb.flyway=11.3.2
|
||||||
|
## # available=11.3.3
|
||||||
|
## # available=11.3.4
|
||||||
|
|
||||||
|
plugin.io.spring.dependency-management=1.1.7
|
||||||
|
|
||||||
|
version.org.webjars..lodash=4.17.21
|
||||||
Reference in New Issue
Block a user