Merge pull request 'feat: review backend and frontend' (#3) from feat/v2 into main
Reviewed-on: #3main 2.0.0
commit
b4c1348e3e
|
@ -0,0 +1,14 @@
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=securePassword123
|
||||||
|
DATABASE_HOST=database
|
||||||
|
DATABASE_PORT=5432
|
||||||
|
DATABASE_NAME=pluss_db
|
||||||
|
|
||||||
|
MAIL_HOST=TO_FILL#exemple: mail.apes-hdf.org
|
||||||
|
MAIL_PORT=TO_FILL#exemple:587
|
||||||
|
MAIL_FROM=TO_FILL#exemple:ne-pas-repondre@apes-hdf.org
|
||||||
|
MAIL_USERNAME=TO_FILL#exemple:ne-pas-repondre@apes-hdf.org
|
||||||
|
MAIL_PASSWORD=TO_FILL
|
||||||
|
MAIL_ACTIVATE_DEBUG=false# set to true to debug mailing
|
||||||
|
|
||||||
|
FRONTEND_URL=http://localhost:8190/#exemple: https://pluss.apes.fr
|
|
@ -4,6 +4,9 @@
|
||||||
/frontend/.eslintcache
|
/frontend/.eslintcache
|
||||||
/frontend/dist
|
/frontend/dist
|
||||||
/frontend/coverage
|
/frontend/coverage
|
||||||
|
/frontend/yarn.lock
|
||||||
|
|
||||||
|
**/.env
|
||||||
|
|
||||||
# Node dependencies
|
# Node dependencies
|
||||||
node_modules
|
node_modules
|
||||||
|
|
79
README.md
79
README.md
|
@ -1,15 +1,32 @@
|
||||||
# Boussole PLUSS
|
# Boussole PLUSS
|
||||||
|
|
||||||
Le projet backend contient la partie backend de la Boussole PLUSS. Le projet frontend, le frontend communiquant avec le backend.
|
## Contribuer à la Boussole
|
||||||
|
Le projet actuel est prévu pour travailler sur le thème de la production locale.
|
||||||
|
Cet outil peut servir à évaluer des collectifs sur d’autres thématiques. Vous êtes encouragé à y réfléchir et à y
|
||||||
|
travailler. La licence AGPL version 3 prévoit un partage des améliorations que vous porterez à l’outil. Si vous
|
||||||
|
désirez faire un fork de l’outil, contactez-nous : contact@apes-hdf.org
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
Le projet backend contient la partie backend de la Boussole PLUSS. Le projet frontend, le site web communiquant avec le backend.
|
||||||
|
|
||||||
|
|
||||||
|
Copier/coller le fichier `.env_template` en `.env` et modifier les variables d'environnements.
|
||||||
|
|
||||||
En utilisant docker-compose :
|
En utilisant docker-compose :
|
||||||
`docker-compose build` en modifiant au préalable l'argument BACKEND_BASE_URL du fichier
|
`docker-compose build` en modifiant au préalable l'argument BACKEND_BASE_URL du fichier
|
||||||
docker-compose.yml pour spécifier l'URL sur laquelle pointera le backend.
|
docker-compose.yml pour spécifier l'URL sur laquelle pointera le backend.
|
||||||
`docker-compose up` pour lancer le frontend et backend.
|
|
||||||
|
|
||||||
Exemple de configuration Nginx compatible avec le docker-compose de ce projet (URL_EXTERNE est à remplacer
|
Au premier lancement, il faut initialiser la base de donnée. Pour ce faire :
|
||||||
|
|
||||||
|
- `docker-compose up -d database`
|
||||||
|
- `docker exec database psql -U postgres -c "CREATE DATABASE pluss_db ENCODING UTF8" postgres`
|
||||||
|
|
||||||
|
En suite, les autres services peuvent être lancés avec `docker-compose up -d`. La base de données sera remplie automatiquement par le service backend.
|
||||||
|
|
||||||
|
Le site est accessible via le service `frontend`, dans cet exemple du docker-compose.yml, depuis http://localhost:8190.
|
||||||
|
|
||||||
|
Voici un exemple de configuration Nginx compatible avec le docker-compose de ce projet (URL_EXTERNE est à remplacer
|
||||||
par l'URL publique) :
|
par l'URL publique) :
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -46,59 +63,3 @@ server {
|
||||||
```
|
```
|
||||||
|
|
||||||
Ceci est un exemple à ne pas utiliser sur un environnement de production.
|
Ceci est un exemple à ne pas utiliser sur un environnement de production.
|
||||||
|
|
||||||
## Administration
|
|
||||||
|
|
||||||
Par la suite, `__URL__` est l'URL du backend, `__USER__` et `__PASSWORD__` le nom de compte et mot de passe à créer / utiliser.
|
|
||||||
### Créer un nouveau compte
|
|
||||||
```bash
|
|
||||||
curl --location --request POST '__URL__/api/auth/signup' \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data-raw '{
|
|
||||||
"username": "__USER__",
|
|
||||||
"password": "__PASSWORD__"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Se connecter
|
|
||||||
```bash
|
|
||||||
curl --location --request POST '__URL__/api/auth/signin' \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data-raw '{
|
|
||||||
"username": "__USER__",
|
|
||||||
"password": "__PASSWORD__"
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
Cet appel renverra un token à réutiliser pour l'ensemble des requêtes. Par exemple :
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"token": "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJkZW1vIiwiaWF0IjoxNjY1NjY5NjUyLCJleHAiOjE2NjU2NzMyNTJ9.i426thZKL4-JvOt9ZeG2D1O6O-xlSiPoCMiKysHYzCkHVNrnxKetq8xKRNCTnbpLV-wagpOw2g-om34k2jtHIw",
|
|
||||||
"refreshToken": "de73adcb-a5a8-4675-8bb3-5536651be0f9",
|
|
||||||
"id": 11,
|
|
||||||
"username": "demo"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Afficher les balises (axe)
|
|
||||||
```bash
|
|
||||||
curl --location --request GET '__URL__/api/axes' \
|
|
||||||
--header 'Authorization: Bearer TOKEN'
|
|
||||||
```
|
|
||||||
Le TOKEN est à remplacer par celui reçu.
|
|
||||||
|
|
||||||
### Ajouter une question à un axe
|
|
||||||
```bash
|
|
||||||
curl --location --request POST '__URL__/api/questions' \
|
|
||||||
--header 'Authorization: Bearer TOKEN' \
|
|
||||||
--header 'Content-Type: application/json' \
|
|
||||||
--data-raw '
|
|
||||||
{
|
|
||||||
"axe": "URL_BALISE_1",
|
|
||||||
"label": "Question 1",
|
|
||||||
"description": "Description .."
|
|
||||||
}
|
|
||||||
'
|
|
||||||
```
|
|
||||||
Il faut spécifier URL_BALISE_1 comme étant l'URL de la balise 1 récupérer dans la requête précédente.
|
|
||||||
Par exemple : `__URL__/axes/1`
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
POSTGRES_USER=postgres
|
||||||
|
POSTGRES_PASSWORD=LKG+gD96
|
||||||
|
DATABASE_HOST=localhost
|
||||||
|
DATABASE_PORT=5432
|
||||||
|
DATABASE_NAME=pluss_db
|
||||||
|
|
||||||
|
MAIL_HOST=
|
||||||
|
MAIL_PORT=
|
||||||
|
MAIL_FROM=
|
||||||
|
MAIL_USERNAME=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_ACTIVATE_DEBUG=false
|
||||||
|
|
||||||
|
FRONTEND_URL=http://localhost:3000/
|
|
@ -1,4 +1,4 @@
|
||||||
FROM maven:3.8.6-eclipse-temurin-17 as builder
|
FROM maven:3.9.8-eclipse-temurin-21 as builder
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY . /src
|
COPY . /src
|
||||||
|
@ -8,7 +8,7 @@ WORKDIR /app
|
||||||
RUN cp /src/target/bousole-pluss-backend*.jar bousole-pluss-backend.jar
|
RUN cp /src/target/bousole-pluss-backend*.jar bousole-pluss-backend.jar
|
||||||
RUN java -Djarmode=layertools -jar bousole-pluss-backend.jar extract
|
RUN java -Djarmode=layertools -jar bousole-pluss-backend.jar extract
|
||||||
|
|
||||||
FROM eclipse-temurin:17-jre-alpine
|
FROM eclipse-temurin:21-jre-alpine
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
USER root
|
USER root
|
||||||
|
@ -18,4 +18,4 @@ COPY --from=builder /app/dependencies/ ./
|
||||||
COPY --from=builder /app/snapshot-dependencies/ ./
|
COPY --from=builder /app/snapshot-dependencies/ ./
|
||||||
COPY --from=builder /app/spring-boot-loader/ ./
|
COPY --from=builder /app/spring-boot-loader/ ./
|
||||||
COPY --from=builder /app/application/ ./
|
COPY --from=builder /app/application/ ./
|
||||||
ENTRYPOINT java org.springframework.boot.loader.JarLauncher
|
ENTRYPOINT java org.springframework.boot.loader.launch.JarLauncher
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-parent</artifactId>
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
<version>2.7.2</version>
|
<version>3.2.7</version>
|
||||||
<relativePath/> <!-- lookup parent from repository -->
|
<relativePath/> <!-- lookup parent from repository -->
|
||||||
</parent>
|
</parent>
|
||||||
<groupId>fr.itsonus</groupId>
|
<groupId>fr.itsonus</groupId>
|
||||||
|
@ -14,9 +14,9 @@
|
||||||
<name>bousole-pluss-backend</name>
|
<name>bousole-pluss-backend</name>
|
||||||
<description>Backend projet of Bousole PLUSS project</description>
|
<description>Backend projet of Bousole PLUSS project</description>
|
||||||
<properties>
|
<properties>
|
||||||
<java.version>17</java.version>
|
<java.version>21</java.version>
|
||||||
|
|
||||||
<jjwt.version>0.9.1</jjwt.version>
|
<jjwt.version>0.12.6</jjwt.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -31,10 +31,6 @@
|
||||||
<groupId>org.springframework.security</groupId>
|
<groupId>org.springframework.security</groupId>
|
||||||
<artifactId>spring-security-data</artifactId>
|
<artifactId>spring-security-data</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.hateoas</groupId>
|
|
||||||
<artifactId>spring-hateoas</artifactId>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-web</artifactId>
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
@ -52,15 +48,33 @@
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-security</artifactId>
|
<artifactId>spring-boot-starter-security</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.jsonwebtoken</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>jjwt</artifactId>
|
<artifactId>spring-boot-starter-mail</artifactId>
|
||||||
<version>${jjwt.version}</version>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.h2database</groupId>
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
<artifactId>h2</artifactId>
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-jackson</artifactId>
|
||||||
|
<version>${jjwt.version}</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
<scope>runtime</scope>
|
<scope>runtime</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -2,6 +2,11 @@ package fr.itsonus.bousoleplussbackend;
|
||||||
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.web.servlet.LocaleResolver;
|
||||||
|
import org.springframework.web.servlet.i18n.SessionLocaleResolver;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
public class Application {
|
public class Application {
|
||||||
|
@ -9,4 +14,11 @@ public class Application {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(Application.class, args);
|
SpringApplication.run(Application.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LocaleResolver localeResolver() {
|
||||||
|
var slr = new SessionLocaleResolver();
|
||||||
|
slr.setDefaultLocale(Locale.FRANCE);
|
||||||
|
return slr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,72 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.configuration;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.jwt.AuthEntryPointJwt;
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.jwt.AuthTokenFilter;
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsServiceImpl;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
|
||||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
|
||||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableGlobalMethodSecurity(prePostEnabled = true)
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|
||||||
private final UserDetailsServiceImpl userDetailsService;
|
|
||||||
|
|
||||||
private final AuthEntryPointJwt unauthorizedHandler;
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public AuthTokenFilter authenticationJwtTokenFilter() {
|
|
||||||
return new AuthTokenFilter();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
|
|
||||||
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Override
|
|
||||||
public AuthenticationManager authenticationManagerBean() throws Exception {
|
|
||||||
return super.authenticationManagerBean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public PasswordEncoder passwordEncoder() {
|
|
||||||
return new BCryptPasswordEncoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void configure(HttpSecurity http) throws Exception {
|
|
||||||
http
|
|
||||||
.cors().configurationSource(request -> {
|
|
||||||
var configuration = new CorsConfiguration();
|
|
||||||
configuration.setAllowedOrigins(List.of("*"));
|
|
||||||
configuration.setAllowedMethods(List.of("GET", "POST"));
|
|
||||||
configuration.setAllowedHeaders(List.of("*"));
|
|
||||||
return configuration;
|
|
||||||
})
|
|
||||||
.and()
|
|
||||||
.csrf().disable()
|
|
||||||
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
|
|
||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
|
||||||
.authorizeRequests().antMatchers("/auth/**").permitAll()
|
|
||||||
.anyRequest().authenticated();
|
|
||||||
|
|
||||||
http.addFilterBefore(authenticationJwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.controllers;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.NotifyPasswordResetRequest;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.ResetPasswordRequest;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.UpdateAccountRequest;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.UpdatePasswordRequest;
|
||||||
|
import fr.itsonus.bousoleplussbackend.usecase.UserPasswordResetUseCase;
|
||||||
|
import fr.itsonus.bousoleplussbackend.usecase.UserUpdateUseCase;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/account")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AccountController {
|
||||||
|
|
||||||
|
private final UserUpdateUseCase userUpdateUseCase;
|
||||||
|
private final UserPasswordResetUseCase userPasswordResetUseCase;
|
||||||
|
|
||||||
|
@PutMapping
|
||||||
|
public void update(@Valid @RequestBody UpdateAccountRequest request) {
|
||||||
|
userUpdateUseCase.update(request.username(), request.email());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("password")
|
||||||
|
public void update(@Valid @RequestBody UpdatePasswordRequest request) {
|
||||||
|
userUpdateUseCase.updatePassword(request.currentPassword(), request.newPassword(), request.confirmationPassword());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("password/notify-reset-request")
|
||||||
|
public void notifyForPasswordReset(@Valid @RequestBody NotifyPasswordResetRequest request) {
|
||||||
|
userPasswordResetUseCase.notifyForPasswordReset(request.email());
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("password/reset")
|
||||||
|
public void resetPassword(@Valid @RequestBody ResetPasswordRequest request) {
|
||||||
|
userPasswordResetUseCase.resetPassword(request.token(), request.email(), request.newPassword(), request.confirmationPassword());
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,101 +1,85 @@
|
||||||
package fr.itsonus.bousoleplussbackend.controllers;
|
package fr.itsonus.bousoleplussbackend.controllers;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.AuthenticationService;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.RefreshToken;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
import fr.itsonus.bousoleplussbackend.exception.TokenRefreshException;
|
import fr.itsonus.bousoleplussbackend.exception.TokenRefreshException;
|
||||||
import fr.itsonus.bousoleplussbackend.models.RefreshToken;
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.User;
|
|
||||||
import fr.itsonus.bousoleplussbackend.payload.request.LogOutRequest;
|
import fr.itsonus.bousoleplussbackend.payload.request.LogOutRequest;
|
||||||
import fr.itsonus.bousoleplussbackend.payload.request.LoginRequest;
|
import fr.itsonus.bousoleplussbackend.payload.request.LoginRequest;
|
||||||
import fr.itsonus.bousoleplussbackend.payload.request.SignupRequest;
|
import fr.itsonus.bousoleplussbackend.payload.request.RegisterRequest;
|
||||||
import fr.itsonus.bousoleplussbackend.payload.request.TokenRefreshRequest;
|
import fr.itsonus.bousoleplussbackend.payload.request.TokenRefreshRequest;
|
||||||
import fr.itsonus.bousoleplussbackend.payload.response.JwtResponse;
|
import fr.itsonus.bousoleplussbackend.payload.response.JwtResponse;
|
||||||
import fr.itsonus.bousoleplussbackend.payload.response.MessageResponse;
|
import fr.itsonus.bousoleplussbackend.security.jwt.JwtGenerator;
|
||||||
import fr.itsonus.bousoleplussbackend.payload.response.TokenRefreshResponse;
|
|
||||||
import fr.itsonus.bousoleplussbackend.repositories.UserRepository;
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.jwt.JwtUtils;
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.services.RefreshTokenService;
|
import fr.itsonus.bousoleplussbackend.security.services.RefreshTokenService;
|
||||||
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsImpl;
|
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsImpl;
|
||||||
|
import fr.itsonus.bousoleplussbackend.usecase.UserCreationUseCase;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/auth")
|
@RequestMapping("/auth")
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class AuthController {
|
public class AuthController {
|
||||||
|
|
||||||
private final AuthenticationManager authenticationManager;
|
private final DaoAuthenticationProvider userAuthenticationProvider;
|
||||||
|
private final AuthenticationService authenticationService;
|
||||||
private final UserRepository userRepository;
|
private final UserCreationUseCase userCreationUseCase;
|
||||||
|
private final JwtGenerator jwtGenerator;
|
||||||
private final PasswordEncoder encoder;
|
|
||||||
|
|
||||||
private final JwtUtils jwtUtils;
|
|
||||||
|
|
||||||
private final RefreshTokenService refreshTokenService;
|
private final RefreshTokenService refreshTokenService;
|
||||||
|
|
||||||
@PostMapping("/signin")
|
@PostMapping("/login")
|
||||||
public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {
|
public JwtResponse login(@Valid @RequestBody LoginRequest loginRequest) {
|
||||||
|
|
||||||
var authentication = authenticationManager
|
var authentication = userAuthenticationProvider
|
||||||
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()));
|
.authenticate(new UsernamePasswordAuthenticationToken(loginRequest.getEmail(), loginRequest.getPassword()));
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
|
||||||
var userDetails = (UserDetailsImpl) authentication.getPrincipal();
|
var userDetails = (UserDetailsImpl) authentication.getPrincipal();
|
||||||
var jwt = jwtUtils.generateJwtToken(userDetails);
|
var token = jwtGenerator.generateAuthToken(authentication);
|
||||||
var refreshToken = refreshTokenService.createRefreshToken(userDetails.getId());
|
var refreshToken = refreshTokenService.createOrUpdateRefreshToken(userDetails.getId());
|
||||||
|
|
||||||
return ResponseEntity.ok(new JwtResponse(jwt, refreshToken.getToken(), userDetails.getId(),
|
return new JwtResponse(token, refreshToken.getToken());
|
||||||
userDetails.getUsername()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/signup")
|
@PostMapping("/register")
|
||||||
public ResponseEntity<?> registerUser(@Valid @RequestBody SignupRequest signUpRequest) {
|
@ResponseStatus(HttpStatus.CREATED)
|
||||||
if (userRepository.existsByUsername(signUpRequest.getUsername())) {
|
public void registerUser(@Valid @RequestBody RegisterRequest request) {
|
||||||
return ResponseEntity.badRequest().body(new MessageResponse("Error: Username is already taken!"));
|
userCreationUseCase.createUserAndDefaultBundle(request.toUser());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new user's account
|
@PostMapping("/refresh-token")
|
||||||
var user = new User();
|
|
||||||
user.setUsername(signUpRequest.getUsername());
|
|
||||||
user.setPassword(encoder.encode(signUpRequest.getPassword()));
|
|
||||||
userRepository.save(user);
|
|
||||||
|
|
||||||
return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/refreshtoken")
|
|
||||||
public ResponseEntity<?> refreshToken(@Valid @RequestBody TokenRefreshRequest request) {
|
public ResponseEntity<?> refreshToken(@Valid @RequestBody TokenRefreshRequest request) {
|
||||||
String requestRefreshToken = request.getRefreshToken();
|
var requestRefreshToken = request.getRefreshToken();
|
||||||
|
|
||||||
return refreshTokenService.findByToken(requestRefreshToken)
|
return refreshTokenService.findByToken(requestRefreshToken)
|
||||||
.map(refreshTokenService::verifyExpiration)
|
.map(refreshTokenService::verifyExpiration)
|
||||||
.map(RefreshToken::getUser)
|
.map(RefreshToken::getUser)
|
||||||
.map(user -> {
|
.map(user -> {
|
||||||
String token = jwtUtils.generateTokenFromUsername(user.getUsername());
|
var token = jwtGenerator.generateRefreshToken(user.getEmail());
|
||||||
return ResponseEntity.ok(new TokenRefreshResponse(token, requestRefreshToken));
|
return ResponseEntity.ok(token);
|
||||||
})
|
})
|
||||||
.orElseThrow(() -> new TokenRefreshException(requestRefreshToken,
|
.orElseThrow(() -> new TokenRefreshException(requestRefreshToken, "Refresh token unknown"));
|
||||||
"Refresh token is not in database!"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logout")
|
@PostMapping("/logout")
|
||||||
public ResponseEntity<?> logoutUser(@Valid @RequestBody LogOutRequest logOutRequest) {
|
public void logoutUser(@Valid @RequestBody LogOutRequest logOutRequest) {
|
||||||
refreshTokenService.deleteByUserId(logOutRequest.getUserId());
|
refreshTokenService.deleteByUserId(logOutRequest.getUserId());
|
||||||
return ResponseEntity.ok(new MessageResponse("Log out successful!"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/me")
|
@GetMapping("/me")
|
||||||
public ResponseEntity<?> me() {
|
public User me() {
|
||||||
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
return authenticationService.getCurrentUser();
|
||||||
return ResponseEntity.ok(authentication.getPrincipal());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.controllers;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Axe;
|
||||||
|
import fr.itsonus.bousoleplussbackend.usecase.AxeUseCase;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/axes")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AxeController {
|
||||||
|
|
||||||
|
private final AxeUseCase axeRepository;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public Iterable<Axe> findAll() {
|
||||||
|
return axeRepository.findAll();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.controllers;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Bundle;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.BundleCreationRequest;
|
||||||
|
import fr.itsonus.bousoleplussbackend.usecase.BundleUseCase;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
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;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/bundles")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BundleController {
|
||||||
|
|
||||||
|
private final BundleUseCase bundleUseCase;
|
||||||
|
|
||||||
|
@PostMapping
|
||||||
|
public Bundle create(@Valid @RequestBody BundleCreationRequest request) {
|
||||||
|
return bundleUseCase.create(request.label(), request.presentation(), request.questions().stream().map(BundleCreationRequest.QuestionCreationRequest::toQuestion).toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public List<Bundle> findAllForCurrentUser() {
|
||||||
|
return bundleUseCase.findAllForCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
public Bundle findById(@PathVariable("id") Long id) {
|
||||||
|
return bundleUseCase.findById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}/questions/search")
|
||||||
|
public Iterable<Question> findQuestions(@PathVariable Long id, @RequestParam Long axeId) {
|
||||||
|
return bundleUseCase.findQuestions(id, axeId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.controllers;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
import fr.itsonus.bousoleplussbackend.usecase.QuestionUseCase;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/questions")
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QuestionController {
|
||||||
|
|
||||||
|
private final QuestionUseCase questionUseCase;
|
||||||
|
|
||||||
|
@GetMapping("search/defaults")
|
||||||
|
public List<Question> findDefaultsByAxeId(@RequestParam Long axeId) {
|
||||||
|
return questionUseCase.findDefaults(axeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("search")
|
||||||
|
public List<Question> findAllByAxeId(@RequestParam Long axeId) {
|
||||||
|
return questionUseCase.findAllByAxeId(axeId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,58 +1,42 @@
|
||||||
package fr.itsonus.bousoleplussbackend.controllers;
|
package fr.itsonus.bousoleplussbackend.controllers;
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Quiz;
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Quiz;
|
||||||
import fr.itsonus.bousoleplussbackend.models.QuizScore;
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.QuizDetailed;
|
||||||
import fr.itsonus.bousoleplussbackend.models.Response;
|
|
||||||
import fr.itsonus.bousoleplussbackend.payload.request.QuizRequest;
|
import fr.itsonus.bousoleplussbackend.payload.request.QuizRequest;
|
||||||
import fr.itsonus.bousoleplussbackend.repositories.QuestionRepository;
|
import fr.itsonus.bousoleplussbackend.usecase.QuizUseCase;
|
||||||
import fr.itsonus.bousoleplussbackend.repositories.QuizRepository;
|
import jakarta.validation.Valid;
|
||||||
import fr.itsonus.bousoleplussbackend.repositories.QuizScoreRepository;
|
|
||||||
import fr.itsonus.bousoleplussbackend.repositories.ResponseRepository;
|
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/quizzes")
|
@RequestMapping("/quizzes")
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class QuizController {
|
public class QuizController {
|
||||||
|
|
||||||
private QuizRepository quizRepository;
|
private final QuizUseCase quizUseCase;
|
||||||
private ResponseRepository responseRepository;
|
|
||||||
private QuestionRepository questionRepository;
|
|
||||||
private QuizScoreRepository quizScoreRepository;
|
|
||||||
|
|
||||||
@PostMapping("batch") // TODO add to REST representation
|
@PostMapping
|
||||||
public Quiz create(@Valid @RequestBody QuizRequest request) {
|
public Quiz create(@Valid @RequestBody QuizRequest request) {
|
||||||
var quiz = quizRepository.save(new Quiz());
|
return quizUseCase.create(request);
|
||||||
var responsesCreated = request.getResponses().stream().map(response -> {
|
|
||||||
// TODO add correct exception with correct status code
|
|
||||||
var question = questionRepository.findById(response.getQuestionId())
|
|
||||||
.orElseThrow(() -> new NoSuchElementException("No such question with id " + response.getQuestionId()));
|
|
||||||
log.info("Saving {}", response);
|
|
||||||
return responseRepository.save(
|
|
||||||
new Response()
|
|
||||||
.setScore(response.getScore())
|
|
||||||
.setComment(response.getComment())
|
|
||||||
.setQuiz(quiz)
|
|
||||||
.setQuestion(question));
|
|
||||||
}).collect(Collectors.toSet());
|
|
||||||
quiz.setResponses(responsesCreated);
|
|
||||||
return quiz;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("{id}/scores")
|
@GetMapping("search")
|
||||||
public Iterable<QuizScore> findScores(@PathVariable("id") Long id) {
|
public Page<QuizDetailed> findAllForCurrentUser(@RequestParam Long bundleId, Pageable pageable) {
|
||||||
return this.quizScoreRepository.findAllByQuizId(id);
|
return this.quizUseCase.findAllForCurrentUser(bundleId, pageable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("{id}")
|
||||||
|
public QuizDetailed findById(@PathVariable("id") Long id) {
|
||||||
|
return this.quizUseCase.findById(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.auth;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.spi.UserCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.exception.NoCurrentUserException;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AuthenticationService {
|
||||||
|
|
||||||
|
private final UserCacheRepository userCacheRepository;
|
||||||
|
|
||||||
|
public User getCurrentUser() {
|
||||||
|
var authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||||
|
var userDetails = (UserDetails) authentication.getPrincipal();
|
||||||
|
return userCacheRepository.findByEmail(userDetails.getUsername())
|
||||||
|
.orElseThrow(NoCurrentUserException::new);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.auth.exception;
|
||||||
|
|
||||||
|
public class AlreadyExistingUserException extends RuntimeException {
|
||||||
|
public AlreadyExistingUserException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.auth.model;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RefreshToken {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private User user;
|
||||||
|
private String token;
|
||||||
|
private Instant expiryDate;
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.auth.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class User {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String email;
|
||||||
|
private String username;
|
||||||
|
@JsonIgnore
|
||||||
|
private String password;
|
||||||
|
@JsonIgnore
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.auth.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.RefreshToken;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface RefreshTokenCacheRepository {
|
||||||
|
Optional<RefreshToken> findByToken(String token);
|
||||||
|
|
||||||
|
Optional<RefreshToken> findByUser(User user);
|
||||||
|
|
||||||
|
void deleteByUserId(long userId);
|
||||||
|
|
||||||
|
RefreshToken save(RefreshToken refreshToken);
|
||||||
|
|
||||||
|
void delete(RefreshToken refreshToken);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.auth.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface UserCacheRepository {
|
||||||
|
Optional<User> findByEmail(String email);
|
||||||
|
|
||||||
|
User save(User user);
|
||||||
|
|
||||||
|
Boolean existsByEmail(String email);
|
||||||
|
|
||||||
|
Optional<User> findById(Long id);
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.notification;
|
||||||
|
|
||||||
|
public interface NotificationService {
|
||||||
|
|
||||||
|
void notify(String from, String to, String subject, String text);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
public class Axe {
|
||||||
|
private Long id;
|
||||||
|
private Integer identifier;
|
||||||
|
private String shortTitle;
|
||||||
|
private String title;
|
||||||
|
private String description;
|
||||||
|
private String color;
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
public class AxeResponses {
|
||||||
|
|
||||||
|
private Integer axeIdentifier;
|
||||||
|
private Double average;
|
||||||
|
private List<QuizResponse> responses;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
public class Bundle {
|
||||||
|
private Long id;
|
||||||
|
private String label;
|
||||||
|
private String presentation;
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
private Date lastQuizzDate;
|
||||||
|
private Integer numberOfQuizzes;
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder(toBuilder = true)
|
||||||
|
public class Question {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private String label;
|
||||||
|
private String description;
|
||||||
|
@JsonIgnore
|
||||||
|
private Long axeId;
|
||||||
|
@JsonIgnore
|
||||||
|
private Integer index;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class Quiz {
|
||||||
|
|
||||||
|
private Long id;
|
||||||
|
private Date createdDate;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class QuizDetailed {
|
||||||
|
private Long id;
|
||||||
|
private Date createdDate;
|
||||||
|
private List<AxeResponses> axes = new ArrayList<>();
|
||||||
|
|
||||||
|
public static QuizDetailed from(Quiz quiz, List<QuizResponse> responses) {
|
||||||
|
var resume = new QuizDetailed();
|
||||||
|
resume.setId(quiz.getId());
|
||||||
|
resume.setCreatedDate(quiz.getCreatedDate());
|
||||||
|
var sortedResponses = new HashMap<Integer, AxeResponses>();
|
||||||
|
responses.forEach(quizResponse -> sortedResponses.compute(quizResponse.axeIdentifier(), (k, v) -> {
|
||||||
|
var axeResponses = Objects.requireNonNullElseGet(v, () -> AxeResponses.builder().axeIdentifier(k).responses(new ArrayList<>()).build());
|
||||||
|
axeResponses.getResponses().add(quizResponse);
|
||||||
|
return axeResponses;
|
||||||
|
}));
|
||||||
|
resume.axes = sortedResponses.values().stream().toList();
|
||||||
|
resume.axes.forEach(axeResponses -> {
|
||||||
|
var average = axeResponses.getResponses().stream().mapToDouble(QuizResponse::score).average().orElse(0.0);
|
||||||
|
axeResponses.setAverage(average);
|
||||||
|
});
|
||||||
|
return resume;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.model;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
|
||||||
|
public record QuizResponse(@JsonIgnore Integer axeIdentifier, String question, Short score, String comment) {
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Axe;
|
||||||
|
|
||||||
|
public interface AxeCacheRepository {
|
||||||
|
Iterable<Axe> findAll();
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Bundle;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface BundleCacheRepository {
|
||||||
|
|
||||||
|
Bundle findByIdAndUserId(Long id, Long userId);
|
||||||
|
|
||||||
|
List<Bundle> findAllByUser(User user);
|
||||||
|
|
||||||
|
Bundle save(String label, String description, List<Question> questions, User user);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface QuestionCacheRepository {
|
||||||
|
List<Question> findQuestions(Long axeId, Long bundleId);
|
||||||
|
|
||||||
|
List<Question> findDefaultQuestions();
|
||||||
|
|
||||||
|
List<Question> findDefaultQuestions(Long axeId);
|
||||||
|
|
||||||
|
List<Question> findAllByAxeId(Long axeId);
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Quiz;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface QuizCacheRepository {
|
||||||
|
|
||||||
|
Optional<Quiz> findById(Long id);
|
||||||
|
|
||||||
|
Optional<Quiz> findByIdAndUserId(Long id, Long userId);
|
||||||
|
|
||||||
|
Page<Quiz> findAllByBundleId(Long bundleId, Pageable pageable);
|
||||||
|
|
||||||
|
Quiz createNew(Long bundleId, User user);
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.domain.quiz.spi;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Quiz;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.QuizResponse;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.ResponseRequest;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public interface ResponseCacheRepository {
|
||||||
|
|
||||||
|
List<QuizResponse> findAllByQuizId(Long quizId);
|
||||||
|
|
||||||
|
void saveQuizzResponses(Long bundleId, Quiz quiz, Set<ResponseRequest> responses);
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.exception;
|
||||||
|
|
||||||
|
public class InvalidPasswordException extends RuntimeException {
|
||||||
|
public InvalidPasswordException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.exception;
|
||||||
|
|
||||||
|
public class NoCurrentUserException extends RuntimeException {
|
||||||
|
public NoCurrentUserException() {
|
||||||
|
super("No current user logged");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,7 @@
|
||||||
package fr.itsonus.bousoleplussbackend.exception;
|
package fr.itsonus.bousoleplussbackend.exception;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
|
||||||
|
|
||||||
@ResponseStatus(HttpStatus.FORBIDDEN)
|
|
||||||
public class TokenRefreshException extends RuntimeException {
|
public class TokenRefreshException extends RuntimeException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
public TokenRefreshException(String token, String message) {
|
public TokenRefreshException(String token, String message) {
|
||||||
super(String.format("Failed for [%s]: %s", token, message));
|
super(String.format("Failed for [%s]: %s", token, message));
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.exception;
|
||||||
|
|
||||||
|
public class UnknownEmailException extends RuntimeException {
|
||||||
|
|
||||||
|
public UnknownEmailException() {
|
||||||
|
super("Cet e-mail est inconnu.");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,118 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.exception.handlers;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.exception.AlreadyExistingUserException;
|
||||||
|
import fr.itsonus.bousoleplussbackend.exception.InvalidPasswordException;
|
||||||
|
import fr.itsonus.bousoleplussbackend.exception.NoCurrentUserException;
|
||||||
|
import fr.itsonus.bousoleplussbackend.exception.TokenRefreshException;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.response.ApiError;
|
||||||
|
import fr.itsonus.bousoleplussbackend.exception.UnknownEmailException;
|
||||||
|
import jakarta.validation.ConstraintViolationException;
|
||||||
|
import org.springframework.dao.DataIntegrityViolationException;
|
||||||
|
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.client.RestClientResponseException;
|
||||||
|
import org.springframework.web.context.request.WebRequest;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
@ControllerAdvice
|
||||||
|
public class ExceptionsHandler extends ResponseEntityExceptionHandler {
|
||||||
|
|
||||||
|
@ExceptionHandler({ConstraintViolationException.class})
|
||||||
|
public ResponseEntity<ApiError> handleValidationExceptions(ConstraintViolationException ex) {
|
||||||
|
var errors = new ArrayList<ApiError.FieldError>();
|
||||||
|
ex.getConstraintViolations().forEach(error -> errors.add(new ApiError.FieldError(error.getMessage(), error.getPropertyPath().toString())));
|
||||||
|
return new ApiError("Saisie invalide", errors).toResponse(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({AlreadyExistingUserException.class})
|
||||||
|
public ResponseEntity<ApiError> handleConflictException(RuntimeException ex) {
|
||||||
|
return new ApiError(ex.getMessage()).toResponse(HttpStatus.CONFLICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({DataIntegrityViolationException.class})
|
||||||
|
public ResponseEntity<ApiError> handleIntegrityViolationExceptions(DataIntegrityViolationException ex) {
|
||||||
|
return new ApiError("Saisie invalide: " + ex.getMessage()).toResponse(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({InvalidPasswordException.class, UnknownEmailException.class})
|
||||||
|
public ResponseEntity<ApiError> handleBadRequestException(RuntimeException ex) {
|
||||||
|
return new ApiError(ex.getMessage()).toResponse(HttpStatus.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({ResourceNotFoundException.class})
|
||||||
|
public ResponseEntity<ApiError> handleResourceNotFoundException(ResourceNotFoundException ex) {
|
||||||
|
return new ApiError(ex.getMessage()).toResponse(HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({AuthenticationException.class})
|
||||||
|
public ResponseEntity<ApiError> handleAuthenticationException(Exception ex) {
|
||||||
|
return new ApiError("Authentication failed: " + ex.getMessage()).toResponse(HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({AccessDeniedException.class, TokenRefreshException.class})
|
||||||
|
public ResponseEntity<ApiError> handleAccessDeniedException(Exception ex) {
|
||||||
|
return new ApiError("Authentication needed: " + ex.getMessage()).toResponse(HttpStatus.FORBIDDEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({BadCredentialsException.class})
|
||||||
|
public ResponseEntity<ApiError> handleBadCredentialsException(Exception ex) {
|
||||||
|
logger.error("Exception raised", ex);
|
||||||
|
return new ApiError("Vos identifiants sont incorrects.").toResponse(HttpStatus.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({NoCurrentUserException.class})
|
||||||
|
public ResponseEntity<ApiError> handleInternalErrorException(RuntimeException ex) {
|
||||||
|
logger.error("Internal error", ex);
|
||||||
|
return new ApiError(ex.getMessage()).toResponse(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({RestClientResponseException.class})
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<ApiError> handleHttpClientErrorException(RestClientResponseException ex) {
|
||||||
|
return new ApiError(ex.getStatusText()).toResponse(ex.getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
|
||||||
|
HttpHeaders headers,
|
||||||
|
HttpStatusCode status, WebRequest request) {
|
||||||
|
var errors = new ArrayList<ApiError.FieldError>();
|
||||||
|
ex.getBindingResult().getFieldErrors()
|
||||||
|
.forEach(error -> errors.add(new ApiError.FieldError(error.getDefaultMessage(), error.getField())));
|
||||||
|
ex.getBindingResult().getGlobalErrors()
|
||||||
|
.forEach(error -> errors.add(new ApiError.FieldError(error.getDefaultMessage(), error.getObjectName())));
|
||||||
|
|
||||||
|
var apiError = new ApiError("Saisie invalide", errors);
|
||||||
|
return ResponseEntity.status(status).body(apiError);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler({Throwable.class})
|
||||||
|
@ResponseBody
|
||||||
|
public ResponseEntity<ApiError> handleGlobally(Throwable ex) {
|
||||||
|
if (logger.isDebugEnabled()) {
|
||||||
|
logger.warn(ex.getMessage(), ex);
|
||||||
|
} else {
|
||||||
|
logger.warn(ex.getMessage());
|
||||||
|
}
|
||||||
|
return new ApiError(ex.getMessage()).toResponse(HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body, HttpHeaders headers,
|
||||||
|
HttpStatusCode statusCode, WebRequest request) {
|
||||||
|
logger.debug("An exception occurred", ex);
|
||||||
|
return super.handleExceptionInternal(ex, new ApiError(ex.getMessage()), headers, statusCode, request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.notification;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.notification.NotificationService;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.mail.SimpleMailMessage;
|
||||||
|
import org.springframework.mail.javamail.JavaMailSender;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SpringMailService implements NotificationService {
|
||||||
|
|
||||||
|
private final JavaMailSender mailSender;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void notify(String from, String to, String subject, String text) {
|
||||||
|
var message = new SimpleMailMessage();
|
||||||
|
message.setFrom(from);
|
||||||
|
message.setTo(to);
|
||||||
|
message.setSubject(subject);
|
||||||
|
message.setText(text);
|
||||||
|
mailSender.send(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresRefreshTokenDto;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresUserDto;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface RefreshTokenCachePostgresRepository extends Repository<PostgresRefreshTokenDto, Long> {
|
||||||
|
|
||||||
|
Optional<PostgresRefreshTokenDto> findByToken(String token);
|
||||||
|
|
||||||
|
Optional<PostgresRefreshTokenDto> findByUser(PostgresUserDto user);
|
||||||
|
|
||||||
|
void deleteByUserId(long userId);
|
||||||
|
|
||||||
|
PostgresRefreshTokenDto save(PostgresRefreshTokenDto refreshToken);
|
||||||
|
|
||||||
|
void delete(PostgresRefreshTokenDto refreshToken);
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.RefreshToken;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.spi.RefreshTokenCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresRefreshTokenDto;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresUserDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class RefreshTokenCacheProxyRepository implements RefreshTokenCacheRepository {
|
||||||
|
|
||||||
|
private RefreshTokenCachePostgresRepository refreshTokenRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<RefreshToken> findByToken(String token) {
|
||||||
|
return refreshTokenRepository.findByToken(token).map(PostgresRefreshTokenDto::toRefreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<RefreshToken> findByUser(User user) {
|
||||||
|
return refreshTokenRepository.findByUser(new PostgresUserDto(user)).map(PostgresRefreshTokenDto::toRefreshToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteByUserId(long userId) {
|
||||||
|
refreshTokenRepository.deleteByUserId(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RefreshToken save(RefreshToken refreshToken) {
|
||||||
|
User user = refreshToken.getUser();
|
||||||
|
var userDto = new PostgresUserDto(user);
|
||||||
|
var refreshTokenDto = new PostgresRefreshTokenDto(refreshToken);
|
||||||
|
refreshTokenDto.setUser(userDto);
|
||||||
|
return refreshTokenRepository.save(refreshTokenDto).toRefreshToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void delete(RefreshToken refreshToken) {
|
||||||
|
refreshTokenRepository.delete(new PostgresRefreshTokenDto(refreshToken));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresUserDto;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface UserCachePostgresRepository extends Repository<PostgresUserDto, Long> {
|
||||||
|
Optional<PostgresUserDto> findByEmail(String email);
|
||||||
|
|
||||||
|
PostgresUserDto save(PostgresUserDto entity);
|
||||||
|
|
||||||
|
Boolean existsByEmail(String email);
|
||||||
|
|
||||||
|
Optional<PostgresUserDto> findById(Long id);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.spi.UserCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresUserDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class UserCacheProxyRepository implements UserCacheRepository {
|
||||||
|
|
||||||
|
private final UserCachePostgresRepository userRepository;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<User> findByEmail(String email) {
|
||||||
|
return userRepository.findByEmail(email)
|
||||||
|
.map(PostgresUserDto::toUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public User save(User user) {
|
||||||
|
var postgresUserDto = new PostgresUserDto(user);
|
||||||
|
return userRepository.save(postgresUserDto).toUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean existsByEmail(String email) {
|
||||||
|
return userRepository.existsByEmail(email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<User> findById(Long id) {
|
||||||
|
return userRepository.findById(id)
|
||||||
|
.map(PostgresUserDto::toUser);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +1,19 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Axe;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.OneToMany;
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
import javax.validation.constraints.Size;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -20,8 +21,8 @@ import java.util.Set;
|
||||||
@Setter
|
@Setter
|
||||||
@ToString
|
@ToString
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Entity
|
@Entity(name = "axe")
|
||||||
public class Axe {
|
public class PostgresAxeDto {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
|
@ -46,13 +47,24 @@ public class Axe {
|
||||||
@OneToMany(mappedBy = "axe")
|
@OneToMany(mappedBy = "axe")
|
||||||
@ToString.Exclude
|
@ToString.Exclude
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Set<Question> questions;
|
private Set<PostgresQuestionDto> questions;
|
||||||
|
|
||||||
|
public Axe toAxe() {
|
||||||
|
return Axe.builder()
|
||||||
|
.id(id)
|
||||||
|
.identifier(identifier)
|
||||||
|
.shortTitle(shortTitle)
|
||||||
|
.title(title)
|
||||||
|
.description(description)
|
||||||
|
.color(color)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
Axe axe = (Axe) o;
|
PostgresAxeDto axe = (PostgresAxeDto) o;
|
||||||
return id != null && Objects.equals(id, axe.id);
|
return id != null && Objects.equals(id, axe.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Bundle;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@Entity(name = "bundle")
|
||||||
|
public class PostgresBundleDto {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 50)
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
private String presentation;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "bundle")
|
||||||
|
@ToString.Exclude
|
||||||
|
private Set<PostgresQuestionDto> questions;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "bundle")
|
||||||
|
@ToString.Exclude
|
||||||
|
private Set<PostgresQuizDto> quizzes;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "user_id")
|
||||||
|
private PostgresUserDto user;
|
||||||
|
|
||||||
|
public Bundle toBundle() {
|
||||||
|
return Bundle.builder()
|
||||||
|
.id(id)
|
||||||
|
.label(label)
|
||||||
|
.presentation(presentation)
|
||||||
|
.lastQuizzDate(quizzes != null ? quizzes.stream().max(Comparator.comparing(PostgresQuizDto::getCreatedDate)).map(PostgresQuizDto::getCreatedDate).orElse(null): null)
|
||||||
|
.numberOfQuizzes(quizzes != null ? quizzes.size(): 0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
|
PostgresBundleDto axe = (PostgresBundleDto) o;
|
||||||
|
return id != null && Objects.equals(id, axe.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Entity(name = "question")
|
||||||
|
public class PostgresQuestionDto {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "axe_id")
|
||||||
|
@ToString.Exclude
|
||||||
|
private PostgresAxeDto axe;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 200)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
@Size(max = 500)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "bundle_id")
|
||||||
|
@ToString.Exclude
|
||||||
|
private PostgresBundleDto bundle;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Integer index;
|
||||||
|
|
||||||
|
public Question toQuestion() {
|
||||||
|
return Question.builder()
|
||||||
|
.id(id)
|
||||||
|
.axeId(axe.getId())
|
||||||
|
.label(label)
|
||||||
|
.index(index)
|
||||||
|
.description(description)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
|
var question = (PostgresQuestionDto) o;
|
||||||
|
return id != null && Objects.equals(id, question.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Quiz;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EntityListeners;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToMany;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@NoArgsConstructor
|
||||||
|
@Entity(name = "quiz")
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class PostgresQuizDto {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Column(name = "created_date", nullable = false, updatable = false)
|
||||||
|
@CreatedDate
|
||||||
|
private Date createdDate;
|
||||||
|
|
||||||
|
@OneToMany(mappedBy = "quiz")
|
||||||
|
@ToString.Exclude
|
||||||
|
private Set<PostgresQuizResponseDto> responses;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
@JoinColumn(name = "bundle_id")
|
||||||
|
private PostgresBundleDto bundle;
|
||||||
|
|
||||||
|
public Quiz toQuiz() {
|
||||||
|
var quiz = new Quiz();
|
||||||
|
quiz.setId(id);
|
||||||
|
quiz.setCreatedDate(createdDate);
|
||||||
|
return quiz;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
|
PostgresQuizDto that = (PostgresQuizDto) o;
|
||||||
|
return id != null && Objects.equals(id, that.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,15 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.QuizResponse;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.ManyToOne;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
import jakarta.validation.constraints.Max;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
@ -9,14 +18,6 @@ import lombok.experimental.Accessors;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.validator.constraints.Length;
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.OneToOne;
|
|
||||||
import javax.validation.constraints.Max;
|
|
||||||
import javax.validation.constraints.Min;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
|
@ -24,8 +25,8 @@ import java.util.Objects;
|
||||||
@ToString
|
@ToString
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@Accessors(chain = true)
|
@Accessors(chain = true)
|
||||||
@Entity
|
@Entity(name = "response")
|
||||||
public class Response {
|
public class PostgresQuizResponseDto {
|
||||||
|
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue
|
@GeneratedValue
|
||||||
|
@ -33,12 +34,12 @@ public class Response {
|
||||||
|
|
||||||
@OneToOne
|
@OneToOne
|
||||||
@JoinColumn(name = "question_id")
|
@JoinColumn(name = "question_id")
|
||||||
private Question question;
|
private PostgresQuestionDto question;
|
||||||
|
|
||||||
@ManyToOne
|
@ManyToOne
|
||||||
@JoinColumn(name = "quiz_id")
|
@JoinColumn(name = "quiz_id")
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Quiz quiz;
|
private PostgresQuizDto quiz;
|
||||||
|
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(10)
|
@Max(10)
|
||||||
|
@ -47,11 +48,19 @@ public class Response {
|
||||||
@Length(max = 500)
|
@Length(max = 500)
|
||||||
private String comment;
|
private String comment;
|
||||||
|
|
||||||
|
public QuizResponse toQuizResponse() {
|
||||||
|
return new QuizResponse(
|
||||||
|
getQuestion().getAxe().getIdentifier(),
|
||||||
|
getQuestion().getLabel(),
|
||||||
|
getScore(),
|
||||||
|
getComment());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
Response response = (Response) o;
|
PostgresQuizResponseDto response = (PostgresQuizResponseDto) o;
|
||||||
return id != null && Objects.equals(id, response.id);
|
return id != null && Objects.equals(id, response.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,66 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.RefreshToken;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.JoinColumn;
|
||||||
|
import jakarta.persistence.OneToOne;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Entity(name = "refresh_token")
|
||||||
|
public class PostgresRefreshTokenDto {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@OneToOne
|
||||||
|
@JoinColumn(name = "user_id", referencedColumnName = "id")
|
||||||
|
private PostgresUserDto user;
|
||||||
|
|
||||||
|
@Column(nullable = false, unique = true)
|
||||||
|
private String token;
|
||||||
|
|
||||||
|
@Column(nullable = false)
|
||||||
|
private Instant expiryDate;
|
||||||
|
|
||||||
|
public PostgresRefreshTokenDto(RefreshToken refreshToken) {
|
||||||
|
this.id = refreshToken.getId();
|
||||||
|
this.token = refreshToken.getToken();
|
||||||
|
this.expiryDate = refreshToken.getExpiryDate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public RefreshToken toRefreshToken() {
|
||||||
|
return RefreshToken.builder()
|
||||||
|
.id(id)
|
||||||
|
.user(user.toUser())
|
||||||
|
.token(token)
|
||||||
|
.expiryDate(expiryDate)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
|
PostgresRefreshTokenDto that = (PostgresRefreshTokenDto) o;
|
||||||
|
return id != null && Objects.equals(id, that.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.models;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.EntityListeners;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import jakarta.persistence.UniqueConstraint;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.hibernate.Hibernate;
|
||||||
|
import org.springframework.data.annotation.CreatedDate;
|
||||||
|
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@ToString
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Entity
|
||||||
|
@Table(name = "users",
|
||||||
|
uniqueConstraints = {
|
||||||
|
@UniqueConstraint(columnNames = "username")
|
||||||
|
})
|
||||||
|
@EntityListeners(AuditingEntityListener.class)
|
||||||
|
public class PostgresUserDto {
|
||||||
|
@Id
|
||||||
|
@GeneratedValue
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
@Email
|
||||||
|
@Size(max = 100)
|
||||||
|
@NotBlank
|
||||||
|
@Column(unique = true, nullable = false, length = 100)
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 20)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Size(max = 120)
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
@Column(name = "created_date", nullable = false, updatable = false)
|
||||||
|
@CreatedDate
|
||||||
|
private LocalDateTime createdDate;
|
||||||
|
|
||||||
|
public PostgresUserDto(User user) {
|
||||||
|
this.id = user.getId();
|
||||||
|
this.email = user.getEmail();
|
||||||
|
this.username = user.getUsername();
|
||||||
|
this.password = user.getPassword();
|
||||||
|
}
|
||||||
|
|
||||||
|
public User toUser() {
|
||||||
|
return User.builder()
|
||||||
|
.id(id)
|
||||||
|
.email(email)
|
||||||
|
.username(username)
|
||||||
|
.password(password)
|
||||||
|
.createdAt(createdDate)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
||||||
|
PostgresUserDto user = (PostgresUserDto) o;
|
||||||
|
return id != null && Objects.equals(id, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getClass().hashCode();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresAxeDto;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface AxeCachePostgresRepository extends Repository<PostgresAxeDto, Long> {
|
||||||
|
PostgresAxeDto findById(Long id);
|
||||||
|
|
||||||
|
List<PostgresAxeDto> findAll();
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Axe;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.spi.AxeCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresAxeDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AxeCacheProxyRepository implements AxeCacheRepository {
|
||||||
|
private final AxeCachePostgresRepository axeCachePostgresRepository;
|
||||||
|
|
||||||
|
public Iterable<Axe> findAll() {
|
||||||
|
return axeCachePostgresRepository.findAll()
|
||||||
|
.stream()
|
||||||
|
.map(PostgresAxeDto::toAxe)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresBundleDto;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface BundleCachePostgresRepository extends Repository<PostgresBundleDto, Long> {
|
||||||
|
|
||||||
|
Optional<PostgresBundleDto> findByIdAndUserId(Long id, Long user_id);
|
||||||
|
|
||||||
|
List<PostgresBundleDto> findAllByUserId(Long userId);
|
||||||
|
|
||||||
|
PostgresBundleDto save(PostgresBundleDto postgresBundleDto);
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Bundle;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.spi.BundleCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.UserCachePostgresRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresBundleDto;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuestionDto;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class BundleCacheProxyRepository implements BundleCacheRepository {
|
||||||
|
|
||||||
|
private final BundleCachePostgresRepository bundleCachePostgresRepository;
|
||||||
|
private final QuestionCachePostgresRepository questionCachePostgresRepository;
|
||||||
|
private final AxeCachePostgresRepository axeCachePostgresRepository;
|
||||||
|
private final UserCachePostgresRepository userCachePostgresRepository;
|
||||||
|
|
||||||
|
public Bundle findByIdAndUserId(Long id, Long userId) {
|
||||||
|
return bundleCachePostgresRepository.findByIdAndUserId(id, userId)
|
||||||
|
.map(PostgresBundleDto::toBundle)
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Bundle not found"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Bundle> findAllByUser(User user) {
|
||||||
|
return bundleCachePostgresRepository.findAllByUserId(user.getId())
|
||||||
|
.stream().map(PostgresBundleDto::toBundle)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Bundle save(String label, String presentation, List<Question> questions, User user) {
|
||||||
|
var entity = new PostgresBundleDto();
|
||||||
|
entity.setLabel(label);
|
||||||
|
entity.setPresentation(presentation);
|
||||||
|
entity.setUser(userCachePostgresRepository.findById(user.getId()).orElseThrow());
|
||||||
|
var newEntity = bundleCachePostgresRepository.save(entity);
|
||||||
|
questions.forEach(question -> {
|
||||||
|
var questionEntity = new PostgresQuestionDto();
|
||||||
|
questionEntity.setBundle(newEntity);
|
||||||
|
questionEntity.setDescription(question.getDescription());
|
||||||
|
questionEntity.setLabel(question.getLabel());
|
||||||
|
questionEntity.setAxe(axeCachePostgresRepository.findById(question.getAxeId()));
|
||||||
|
questionEntity.setIndex(question.getIndex());
|
||||||
|
questionCachePostgresRepository.save(questionEntity);
|
||||||
|
});
|
||||||
|
return newEntity.toBundle();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuestionDto;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface QuestionCachePostgresRepository extends Repository<PostgresQuestionDto, Long> {
|
||||||
|
|
||||||
|
List<PostgresQuestionDto> findAllByAxeIdAndBundleIdOrderByIndexAsc(final Long axeId, final Long userId);
|
||||||
|
|
||||||
|
List<PostgresQuestionDto> findAllByBundleIsNull();
|
||||||
|
|
||||||
|
List<PostgresQuestionDto> findAllByAxeIdAndBundleIsNullOrderByIndexAsc(final Long axeId);
|
||||||
|
|
||||||
|
Optional<PostgresQuestionDto> findByIdAndBundleId(Long questionId, Long bundleId);
|
||||||
|
|
||||||
|
PostgresQuestionDto save(PostgresQuestionDto entity);
|
||||||
|
|
||||||
|
List<PostgresQuestionDto> findAllByAxeIdAndBundleIsNotNullOrderByIndexAsc(Long axeId);
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.spi.QuestionCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuestionDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QuestionCacheProxyRepository implements QuestionCacheRepository {
|
||||||
|
|
||||||
|
private final QuestionCachePostgresRepository questionCachePostgresRepository;
|
||||||
|
|
||||||
|
public List<Question> findQuestions(Long axeId, Long bundleId) {
|
||||||
|
return questionCachePostgresRepository.findAllByAxeIdAndBundleIdOrderByIndexAsc(axeId, bundleId)
|
||||||
|
.stream()
|
||||||
|
.map(PostgresQuestionDto::toQuestion).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Question> findDefaultQuestions() {
|
||||||
|
return questionCachePostgresRepository.findAllByBundleIsNull()
|
||||||
|
.stream()
|
||||||
|
.map(PostgresQuestionDto::toQuestion).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Question> findDefaultQuestions(Long axeId) {
|
||||||
|
return questionCachePostgresRepository.findAllByAxeIdAndBundleIsNullOrderByIndexAsc(axeId)
|
||||||
|
.stream()
|
||||||
|
.map(PostgresQuestionDto::toQuestion).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Question> findAllByAxeId(Long axeId) {
|
||||||
|
var questions = questionCachePostgresRepository.findAllByAxeIdAndBundleIsNotNullOrderByIndexAsc(axeId)
|
||||||
|
.stream()
|
||||||
|
.map(PostgresQuestionDto::toQuestion).toList();
|
||||||
|
Set<String> set = new HashSet<>(questions.size());
|
||||||
|
return questions.stream().filter(q -> set.add(q.getLabel() + q.getDescription())).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuizDto;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.nio.channels.FileChannel;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface QuizCachePostgresRepository extends Repository<PostgresQuizDto, Long> {
|
||||||
|
|
||||||
|
Optional<PostgresQuizDto> findById(Long id);
|
||||||
|
|
||||||
|
Page<PostgresQuizDto> findAllByBundleId(Long userId, Pageable pageable);
|
||||||
|
|
||||||
|
List<PostgresQuizDto> findAllByCreatedDateBefore(Date createdDate);
|
||||||
|
|
||||||
|
PostgresQuizDto save(PostgresQuizDto quiz);
|
||||||
|
|
||||||
|
Optional<PostgresQuizDto> findByIdAndBundle_User_Id(Long id, Long userId);
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Quiz;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.spi.QuizCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuizDto;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
|
import org.springframework.data.rest.webmvc.ResourceNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class QuizCacheProxyRepository implements QuizCacheRepository {
|
||||||
|
|
||||||
|
private final QuizCachePostgresRepository quizCachePostgresRepository;
|
||||||
|
private final BundleCachePostgresRepository bundleCachePostgresRepository;
|
||||||
|
|
||||||
|
public Optional<Quiz> findById(Long id) {
|
||||||
|
return quizCachePostgresRepository.findById(id).map(PostgresQuizDto::toQuiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Quiz> findByIdAndUserId(Long id, Long userId) {
|
||||||
|
return quizCachePostgresRepository.findByIdAndBundle_User_Id(id, userId).map(PostgresQuizDto::toQuiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Page<Quiz> findAllByBundleId(Long bundleId, Pageable pageable) {
|
||||||
|
return quizCachePostgresRepository.findAllByBundleId(bundleId, pageable).map(PostgresQuizDto::toQuiz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Quiz createNew(Long bundleId, User user) {
|
||||||
|
var bundle = bundleCachePostgresRepository.findByIdAndUserId(bundleId, user.getId())
|
||||||
|
.orElseThrow(() -> new ResourceNotFoundException("Bundle not found"));
|
||||||
|
var quizDto = new PostgresQuizDto();
|
||||||
|
quizDto.setBundle(bundle);
|
||||||
|
return quizCachePostgresRepository.save(quizDto).toQuiz();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuizResponseDto;
|
||||||
|
import org.springframework.data.repository.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface ResponseCachePostgresRepository extends Repository<PostgresQuizResponseDto, Long> {
|
||||||
|
|
||||||
|
PostgresQuizResponseDto save(PostgresQuizResponseDto quizResponseDto);
|
||||||
|
|
||||||
|
List<PostgresQuizResponseDto> findAllByQuizIdOrderByQuestionIndexAsc(Long quizId);
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.infrastructure.postgres.repositories;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Quiz;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.QuizResponse;
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.spi.ResponseCacheRepository;
|
||||||
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuizResponseDto;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.request.ResponseRequest;
|
||||||
|
import jakarta.transaction.Transactional;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ResponseCacheProxyRepository implements ResponseCacheRepository {
|
||||||
|
|
||||||
|
private final ResponseCachePostgresRepository responseCachePostgresRepository;
|
||||||
|
private final QuestionCachePostgresRepository questionCachePostgresRepository;
|
||||||
|
private final QuizCachePostgresRepository quizCachePostgresRepository;
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void saveQuizzResponses(Long bundleId, Quiz quiz, Set<ResponseRequest> responses) {
|
||||||
|
var quizDto = quizCachePostgresRepository.findById(quiz.getId()).orElseThrow();
|
||||||
|
responses.forEach(response -> {
|
||||||
|
var question = questionCachePostgresRepository.findByIdAndBundleId(response.getQuestionId(), bundleId)
|
||||||
|
.orElseThrow(() -> new NoSuchElementException("No such question with id " + response.getQuestionId()));
|
||||||
|
responseCachePostgresRepository.save(
|
||||||
|
new PostgresQuizResponseDto()
|
||||||
|
.setScore(response.getScore())
|
||||||
|
.setComment(response.getComment())
|
||||||
|
.setQuiz(quizDto)
|
||||||
|
.setQuestion(question));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<QuizResponse> findAllByQuizId(Long quizId) {
|
||||||
|
return responseCachePostgresRepository.findAllByQuizIdOrderByQuestionIndexAsc(quizId)
|
||||||
|
.stream().map(PostgresQuizResponseDto::toQuizResponse).toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,66 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsImpl;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.hibernate.Hibernate;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
import javax.persistence.PrePersist;
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
import javax.validation.constraints.Size;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Entity
|
|
||||||
public class Question {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "axe_id")
|
|
||||||
@ToString.Exclude
|
|
||||||
private Axe axe;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Size(max = 200)
|
|
||||||
private String label;
|
|
||||||
|
|
||||||
@Size(max = 500)
|
|
||||||
private String description;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
|
||||||
var question = (Question) o;
|
|
||||||
return id != null && Objects.equals(id, question.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getClass().hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
@PrePersist
|
|
||||||
public void prePersist() {
|
|
||||||
var userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
|
||||||
this.userId = userDetails.getId();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsImpl;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.hibernate.Hibernate;
|
|
||||||
import org.springframework.data.annotation.CreatedDate;
|
|
||||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.EntityListeners;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.OneToMany;
|
|
||||||
import javax.persistence.PrePersist;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Entity
|
|
||||||
@EntityListeners(AuditingEntityListener.class)
|
|
||||||
public class Quiz {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@Column(name = "created_date", nullable = false, updatable = false)
|
|
||||||
@CreatedDate
|
|
||||||
private Date createdDate;
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
private Long userId;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "quiz")
|
|
||||||
@ToString.Exclude
|
|
||||||
private Set<Response> responses;
|
|
||||||
|
|
||||||
@OneToMany(mappedBy = "quiz")
|
|
||||||
@ToString.Exclude
|
|
||||||
private List<QuizScore> scores;
|
|
||||||
|
|
||||||
@PrePersist
|
|
||||||
public void prePersist() {
|
|
||||||
var userDetails = (UserDetailsImpl) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
|
|
||||||
this.userId = userDetails.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
|
||||||
Quiz that = (Quiz) o;
|
|
||||||
return id != null && Objects.equals(id, that.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getClass().hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.hibernate.annotations.Immutable;
|
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.ManyToOne;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Entity
|
|
||||||
@Immutable
|
|
||||||
public class QuizScore {
|
|
||||||
|
|
||||||
@Id
|
|
||||||
@JsonIgnore
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
private Float scoreAvg;
|
|
||||||
private Integer axeIdentifier;
|
|
||||||
// @JsonIgnore
|
|
||||||
// private Long quizId;
|
|
||||||
|
|
||||||
@ManyToOne
|
|
||||||
@JoinColumn(name = "quiz_id")
|
|
||||||
@JsonIgnore
|
|
||||||
private Quiz quiz;
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.hibernate.Hibernate;
|
|
||||||
|
|
||||||
import javax.persistence.Column;
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.JoinColumn;
|
|
||||||
import javax.persistence.OneToOne;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Entity
|
|
||||||
public class RefreshToken {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@OneToOne
|
|
||||||
@JoinColumn(name = "user_id", referencedColumnName = "id")
|
|
||||||
private User user;
|
|
||||||
|
|
||||||
@Column(nullable = false, unique = true)
|
|
||||||
private String token;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
|
||||||
private Instant expiryDate;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
|
||||||
RefreshToken that = (RefreshToken) o;
|
|
||||||
return id != null && Objects.equals(id, that.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getClass().hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.models;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
|
||||||
import org.hibernate.Hibernate;
|
|
||||||
|
|
||||||
import javax.persistence.Entity;
|
|
||||||
import javax.persistence.GeneratedValue;
|
|
||||||
import javax.persistence.GenerationType;
|
|
||||||
import javax.persistence.Id;
|
|
||||||
import javax.persistence.Table;
|
|
||||||
import javax.persistence.UniqueConstraint;
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
import javax.validation.constraints.Size;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
@Setter
|
|
||||||
@ToString
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Entity
|
|
||||||
@Table(name = "users",
|
|
||||||
uniqueConstraints = {
|
|
||||||
@UniqueConstraint(columnNames = "username")
|
|
||||||
})
|
|
||||||
public class User {
|
|
||||||
@Id
|
|
||||||
@GeneratedValue
|
|
||||||
private Long id;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Size(max = 20)
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Size(max = 120)
|
|
||||||
private String password;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) return false;
|
|
||||||
User user = (User) o;
|
|
||||||
return id != null && Objects.equals(id, user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return getClass().hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.quiz.model.Question;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record BundleCreationRequest(@NotBlank @Length(max = 50) String label, @Length(max = 100) String presentation,
|
||||||
|
@Valid @NotEmpty List<QuestionCreationRequest> questions) {
|
||||||
|
|
||||||
|
public record QuestionCreationRequest(@NotEmpty @Length(max = 200) String label,
|
||||||
|
@Length(max = 500) String description,
|
||||||
|
@NotNull Long axeId,
|
||||||
|
@NotNull Integer index) {
|
||||||
|
public Question toQuestion() {
|
||||||
|
return Question.builder().axeId(axeId).label(label).index(index).description(description).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,14 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.request;
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class LoginRequest {
|
public class LoginRequest {
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String username;
|
@Email
|
||||||
|
private String email;
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
private String password;
|
private String password;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
|
||||||
|
public record NotifyPasswordResetRequest(@Email String email) {
|
||||||
|
}
|
|
@ -1,14 +1,12 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.request;
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
import lombok.Data;
|
import jakarta.validation.Valid;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
import javax.validation.Valid;
|
|
||||||
import javax.validation.constraints.NotEmpty;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
@Data
|
public record QuizRequest(@NotNull Long bundleId,
|
||||||
public class QuizRequest {
|
@NotEmpty Set<@Valid ResponseRequest> responses) {
|
||||||
@NotEmpty
|
|
||||||
private Set<@Valid ResponseRequest> responses;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.domain.auth.model.User;
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.validation.Password;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class RegisterRequest {
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Email
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Length(min = 3, max = 20)
|
||||||
|
private String username;
|
||||||
|
|
||||||
|
@NotBlank
|
||||||
|
@Password
|
||||||
|
private String password;
|
||||||
|
|
||||||
|
public User toUser() {
|
||||||
|
return User.builder()
|
||||||
|
.email(email)
|
||||||
|
.username(username)
|
||||||
|
.password(password)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.validation.Password;
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record ResetPasswordRequest(@NotBlank String token, @Email String email,
|
||||||
|
@NotBlank @Password String newPassword,
|
||||||
|
@NotBlank String confirmationPassword) {
|
||||||
|
}
|
|
@ -1,9 +1,8 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.request;
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class ResponseRequest {
|
public class ResponseRequest {
|
||||||
@NotNull
|
@NotNull
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.request;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
import javax.validation.constraints.Size;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
public class SignupRequest {
|
|
||||||
@NotBlank
|
|
||||||
@Size(min = 3, max = 20)
|
|
||||||
private String username;
|
|
||||||
|
|
||||||
@NotBlank
|
|
||||||
@Size(min = 6, max = 40)
|
|
||||||
private String password;
|
|
||||||
}
|
|
|
@ -1,9 +1,8 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.request;
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import javax.validation.constraints.NotBlank;
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class TokenRefreshRequest {
|
public class TokenRefreshRequest {
|
||||||
@NotBlank
|
@NotBlank
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
|
||||||
|
public record UpdateAccountRequest(@NotBlank String username, @NotNull @Email String email) {
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.request;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.payload.validation.Password;
|
||||||
|
import jakarta.validation.constraints.NotBlank;
|
||||||
|
|
||||||
|
public record UpdatePasswordRequest(@NotBlank String currentPassword,
|
||||||
|
@NotBlank @Password String newPassword,
|
||||||
|
@NotBlank String confirmationPassword) {
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.response;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public record ApiError(String message, @JsonInclude(JsonInclude.Include.NON_EMPTY) List<FieldError> fieldErrors) {
|
||||||
|
|
||||||
|
public ApiError(String message) {
|
||||||
|
this(message, List.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseEntity<ApiError> toResponse(HttpStatus status) {
|
||||||
|
return ResponseEntity.status(status).body(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResponseEntity<ApiError> toResponse(HttpStatusCode status) {
|
||||||
|
return ResponseEntity.status(status).body(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public record FieldError(String detail, @JsonInclude(JsonInclude.Include.NON_EMPTY) String... fields) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,15 +1,17 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.response;
|
package fr.itsonus.bousoleplussbackend.payload.response;
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Question;
|
import fr.itsonus.bousoleplussbackend.infrastructure.postgres.models.PostgresQuestionDto;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import org.springframework.hateoas.RepresentationModel;
|
import org.springframework.hateoas.RepresentationModel;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class AxeWithQuestion extends RepresentationModel<AxeWithQuestion> {
|
public class AxeWithQuestion extends RepresentationModel<AxeWithQuestion> {
|
||||||
|
|
||||||
private Integer identifier;
|
private Integer identifier;
|
||||||
private String shortTitle;
|
private String shortTitle;
|
||||||
private String title;
|
private String title;
|
||||||
private String color;
|
private String color;
|
||||||
private Iterable<Question> questions;
|
private Iterable<PostgresQuestionDto> questions;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package fr.itsonus.bousoleplussbackend.payload.response;
|
package fr.itsonus.bousoleplussbackend.payload.response;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.security.jwt.Token;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class JwtResponse {
|
public class JwtResponse {
|
||||||
|
private Token token;
|
||||||
private static String type = "Bearer";
|
|
||||||
private String token;
|
|
||||||
private String refreshToken;
|
private String refreshToken;
|
||||||
private Long id;
|
|
||||||
private String username;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import lombok.Data;
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
public class TokenRefreshResponse {
|
public class TokenRefreshResponse {
|
||||||
|
|
||||||
private static String tokenType = "Bearer";
|
private String tokenType;
|
||||||
private String accessToken;
|
private String accessToken;
|
||||||
private String refreshToken;
|
private String refreshToken;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.Constraint;
|
||||||
|
import jakarta.validation.Payload;
|
||||||
|
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Constraint(validatedBy = PasswordConstraintValidator.class)
|
||||||
|
@Target( { ElementType.METHOD, ElementType.FIELD })
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
public @interface Password {
|
||||||
|
String message() default "Votre mot de passe doit fait au moins 8 caractères, et être composé d’au moins une majuscule, une minuscule, un chiffre de 0 à 9, et un caractère spécial parmi @?!#$&;,:";
|
||||||
|
Class<?>[] groups() default {};
|
||||||
|
Class<? extends Payload>[] payload() default {};
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.payload.validation;
|
||||||
|
|
||||||
|
import jakarta.validation.ConstraintValidator;
|
||||||
|
import jakarta.validation.ConstraintValidatorContext;
|
||||||
|
|
||||||
|
public class PasswordConstraintValidator implements ConstraintValidator<Password, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid(String password, ConstraintValidatorContext cxt) {
|
||||||
|
return password != null && password.matches("^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@?!#$&;,:])[A-Za-z\\d@?!#$&;,:]{8,}$");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,11 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.projections;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Question;
|
|
||||||
import org.springframework.data.rest.core.config.Projection;
|
|
||||||
|
|
||||||
@Projection(types = {Question.class})
|
|
||||||
public interface QuestionProj {
|
|
||||||
Long getId();
|
|
||||||
String getLabel();
|
|
||||||
String getDescription();
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.projections;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Quiz;
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Response;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.data.rest.core.config.Projection;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
@Projection(name = "responseWithQuestion", types = { Quiz.class })
|
|
||||||
public interface QuizWithResponses {
|
|
||||||
|
|
||||||
String getCreatedDate();
|
|
||||||
|
|
||||||
@Value("#{target.getResponses()}")
|
|
||||||
Set<ResponseWithQuestion> getResponses();
|
|
||||||
|
|
||||||
// @Value("#{target.getResponses().stream().collect(Collectors.toMap(value -> value, value -> value.length()))}")
|
|
||||||
// Map<Integer, ResponseWithQuestion> getResponses();
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.projections;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Quiz;
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.QuizScore;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.data.rest.core.config.Projection;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Projection(name = "quizWithScore", types = {Quiz.class})
|
|
||||||
public interface QuizWithScore {
|
|
||||||
|
|
||||||
Long getId();
|
|
||||||
|
|
||||||
Date getCreatedDate();
|
|
||||||
|
|
||||||
@Value("#{target.getScores()}")
|
|
||||||
List<QuizScore> getScores();
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.projections;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Response;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.data.rest.core.config.Projection;
|
|
||||||
|
|
||||||
@Projection(name = "responseWithQuestion", types = { Response.class })
|
|
||||||
public interface ResponseWithQuestion {
|
|
||||||
|
|
||||||
String getComment();
|
|
||||||
Short getScore();
|
|
||||||
|
|
||||||
@Value("#{target.getQuestion().getLabel()}")
|
|
||||||
String getQuestion();
|
|
||||||
|
|
||||||
@Value("#{target.getQuestion().getAxe().getIdentifier()}")
|
|
||||||
Integer getAxeIdentifier();
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Axe;
|
|
||||||
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
|
|
||||||
import org.springframework.data.repository.CrudRepository;
|
|
||||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
|
||||||
|
|
||||||
@RepositoryRestResource
|
|
||||||
public interface AxeRepository extends CrudRepository<Axe, Long>, JpaSpecificationExecutor<Axe> {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Question;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.CrudRepository;
|
|
||||||
import org.springframework.data.repository.query.Param;
|
|
||||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
|
||||||
import org.springframework.data.rest.core.annotation.RestResource;
|
|
||||||
|
|
||||||
@RepositoryRestResource
|
|
||||||
public interface QuestionRepository extends CrudRepository<Question, Long> {
|
|
||||||
|
|
||||||
@RestResource(path="byAxeId", rel="byAxeId")
|
|
||||||
@Query("SELECT q FROM Question q JOIN q.axe a WHERE q.axe.id = :id AND user_id = ?#{principal.id}")
|
|
||||||
Iterable<Question> findAllByAxeId(@Param("id") final Long id);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Quiz;
|
|
||||||
import fr.itsonus.bousoleplussbackend.projections.QuizWithScore;
|
|
||||||
import org.springframework.data.domain.Page;
|
|
||||||
import org.springframework.data.domain.Pageable;
|
|
||||||
import org.springframework.data.jpa.repository.Query;
|
|
||||||
import org.springframework.data.repository.PagingAndSortingRepository;
|
|
||||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
|
||||||
import org.springframework.data.rest.core.annotation.RestResource;
|
|
||||||
|
|
||||||
@RepositoryRestResource(excerptProjection = QuizWithScore.class)
|
|
||||||
public interface QuizRepository extends PagingAndSortingRepository<Quiz, Long> {
|
|
||||||
|
|
||||||
@RestResource(path = "me", rel = "me")
|
|
||||||
@Query("SELECT q FROM Quiz q WHERE q.userId = ?#{principal.id}")
|
|
||||||
Page<Quiz> findAllWithScoresOfCurrentUser(final Pageable pageable);
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.QuizScore;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public interface QuizScoreRepository extends JpaRepository<QuizScore, Long> {
|
|
||||||
|
|
||||||
Iterable<QuizScore> findAllByQuizId(Long quizId);
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.RefreshToken;
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.User;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.data.jpa.repository.Modifying;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
|
|
||||||
Optional<RefreshToken> findByToken(String token);
|
|
||||||
|
|
||||||
@Modifying
|
|
||||||
int deleteByUser(User user);
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.Response;
|
|
||||||
import fr.itsonus.bousoleplussbackend.projections.ResponseWithQuestion;
|
|
||||||
import org.springframework.data.repository.CrudRepository;
|
|
||||||
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
|
|
||||||
|
|
||||||
@RepositoryRestResource(excerptProjection = ResponseWithQuestion.class)
|
|
||||||
public interface ResponseRepository extends CrudRepository<Response, Long> {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.repositories;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.models.User;
|
|
||||||
import org.springframework.data.jpa.repository.JpaRepository;
|
|
||||||
import org.springframework.stereotype.Repository;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
@Repository
|
|
||||||
public interface UserRepository extends JpaRepository<User, Long> {
|
|
||||||
Optional<User> findByUsername(String username);
|
|
||||||
|
|
||||||
Boolean existsByUsername(String username);
|
|
||||||
}
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.security;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.annotation.Order;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.data.repository.query.SecurityEvaluationContextExtension;
|
||||||
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(securedEnabled = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Order(1)
|
||||||
|
public class CoreSecurityConfig {
|
||||||
|
|
||||||
|
@Value("${security.cors.allow-origins}")
|
||||||
|
private List<String> corsOrigins;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PasswordEncoder passwordEncoder() {
|
||||||
|
return new BCryptPasswordEncoder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
CorsConfigurationSource corsConfigurationSource() {
|
||||||
|
var configuration = new CorsConfiguration();
|
||||||
|
configuration.setAllowedOrigins(corsOrigins);
|
||||||
|
configuration.setAllowedMethods(List.of("GET", "POST", "PUT", "PATCH", "OPTIONS"));
|
||||||
|
configuration.setAllowedHeaders(List.of("*"));
|
||||||
|
var source = new UrlBasedCorsConfigurationSource();
|
||||||
|
source.registerCorsConfiguration("/**", configuration);
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
|
||||||
|
return new SecurityEvaluationContextExtension();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.security;
|
||||||
|
|
||||||
|
import fr.itsonus.bousoleplussbackend.security.jwt.ExceptionHandlerFilter;
|
||||||
|
import fr.itsonus.bousoleplussbackend.security.jwt.JwtAuthenticationFilter;
|
||||||
|
import fr.itsonus.bousoleplussbackend.security.jwt.JwtGenerator;
|
||||||
|
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsServiceImpl;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.HttpMethod;
|
||||||
|
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
|
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.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||||
|
import org.springframework.security.web.header.HeaderWriterFilter;
|
||||||
|
import org.springframework.web.cors.CorsConfigurationSource;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableMethodSecurity(securedEnabled = true)
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final UserDetailsServiceImpl userDetailsService;
|
||||||
|
private final PasswordEncoder passwordEncoder;
|
||||||
|
private final CorsConfigurationSource corsConfigurationSource;
|
||||||
|
private final JwtGenerator jwtGenerator;
|
||||||
|
private final ExceptionHandlerFilter exceptionHandlerFilter;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
http
|
||||||
|
.cors(cors -> cors.configurationSource(corsConfigurationSource))
|
||||||
|
.csrf(AbstractHttpConfigurer::disable)
|
||||||
|
.sessionManagement(Customizer.withDefaults())
|
||||||
|
.sessionManagement(management -> management.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.securityMatcher("/auth/**", "/account/**", "bundles/**", "/quizzes/**", "/axes/**", "questions/**", "/responses/**")
|
||||||
|
.authorizeHttpRequests(matcher -> matcher
|
||||||
|
.requestMatchers(HttpMethod.POST,
|
||||||
|
"/auth/login", "/auth/register", "/auth/refresh-token",
|
||||||
|
"/account/password/notify-reset-request", "/account/password/reset")
|
||||||
|
.permitAll()
|
||||||
|
.anyRequest().authenticated())
|
||||||
|
.httpBasic(Customizer.withDefaults())
|
||||||
|
.addFilterBefore(exceptionHandlerFilter, HeaderWriterFilter.class)
|
||||||
|
.addFilterBefore(userJwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
return http.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JwtAuthenticationFilter userJwtAuthenticationFilter() {
|
||||||
|
return new JwtAuthenticationFilter(jwtGenerator, userDetailsService);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public DaoAuthenticationProvider userAuthenticationProvider() {
|
||||||
|
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
|
provider.setUserDetailsService(userDetailsService);
|
||||||
|
provider.setPasswordEncoder(passwordEncoder);
|
||||||
|
return provider;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.security;
|
||||||
|
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
public class SecurityConstants {
|
||||||
|
public static final SecretKey JWT_SECRET;
|
||||||
|
|
||||||
|
static {
|
||||||
|
var randomGenerator = new SecureRandom();
|
||||||
|
byte[] randomBytes = new byte[128];
|
||||||
|
randomGenerator.nextBytes(randomBytes);
|
||||||
|
JWT_SECRET = new SecretKeySpec(
|
||||||
|
Base64.getDecoder().decode(new BigInteger(1, randomBytes).toString(16)),
|
||||||
|
"HmacSHA256");
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,45 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.security.jwt;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.http.MediaType;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
public class AuthEntryPointJwt implements AuthenticationEntryPoint {
|
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AuthEntryPointJwt.class);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
logger.error("Unauthorized error: {}", authException.getMessage());
|
|
||||||
|
|
||||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
|
|
||||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
|
|
||||||
final Map<String, Object> body = new HashMap<>();
|
|
||||||
body.put("status", HttpServletResponse.SC_UNAUTHORIZED);
|
|
||||||
body.put("error", "Unauthorized");
|
|
||||||
body.put("message", authException.getMessage());
|
|
||||||
body.put("path", request.getServletPath());
|
|
||||||
|
|
||||||
final ObjectMapper mapper = new ObjectMapper();
|
|
||||||
mapper.writeValue(response.getOutputStream(), body);
|
|
||||||
|
|
||||||
// response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
package fr.itsonus.bousoleplussbackend.security.jwt;
|
|
||||||
|
|
||||||
import fr.itsonus.bousoleplussbackend.security.services.UserDetailsServiceImpl;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
|
||||||
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class AuthTokenFilter extends OncePerRequestFilter {
|
|
||||||
@Autowired
|
|
||||||
private JwtUtils jwtUtils;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private UserDetailsServiceImpl userDetailsService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
try {
|
|
||||||
var jwt = parseJwt(request);
|
|
||||||
if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
|
|
||||||
String username = jwtUtils.getUserNameFromJwtToken(jwt);
|
|
||||||
|
|
||||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
|
||||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null,
|
|
||||||
userDetails.getAuthorities());
|
|
||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
|
||||||
|
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error("Cannot set user authentication: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
filterChain.doFilter(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String parseJwt(HttpServletRequest request) {
|
|
||||||
var headerAuth = request.getHeader("Authorization");
|
|
||||||
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
|
|
||||||
return headerAuth.substring(7);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.security.jwt;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class ExceptionHandlerFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||||
|
try {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
} catch (AuthenticationException e) {
|
||||||
|
response.sendError(HttpStatus.UNAUTHORIZED.value(), e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package fr.itsonus.bousoleplussbackend.security.jwt;
|
||||||
|
|
||||||
|
import jakarta.servlet.FilterChain;
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
|
||||||
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
|
private JwtGenerator jwtGenerator;
|
||||||
|
private UserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
var token = getJWTFromRequest(request);
|
||||||
|
|
||||||
|
if (token != null && jwtGenerator.validateToken(token)) {
|
||||||
|
String subject = jwtGenerator.getSubjectFromJWT(token);
|
||||||
|
UserDetails userDetails = userDetailsService.loadUserByUsername(subject);
|
||||||
|
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
||||||
|
userDetails,
|
||||||
|
null, userDetails.getAuthorities());
|
||||||
|
|
||||||
|
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||||
|
}
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getJWTFromRequest(HttpServletRequest request) {
|
||||||
|
String bearerToken = request.getHeader("Authorization");
|
||||||
|
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
|
||||||
|
return bearerToken.substring(7);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue