diff --git a/back_end/package-lock.json b/back_end/package-lock.json new file mode 100644 index 0000000..fc3a8b4 --- /dev/null +++ b/back_end/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "back_end", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java b/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java index 2d807d7..c96ab6c 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/config/WebSecurityConfig.java @@ -6,11 +6,15 @@ import java.util.Map; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @Configuration @EnableWebSecurity @@ -19,9 +23,10 @@ public class WebSecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http + .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(csrf -> csrf.disable()) .authorizeHttpRequests(auth -> auth - .requestMatchers("/", "/public", "/coach/**").permitAll() // allow coach endpoints + .requestMatchers(HttpMethod.OPTIONS, "/", "/public", "/coach/**").permitAll() // allow coach endpoints .requestMatchers("/admin/**").hasRole("admin") .requestMatchers("/user/**").hasRole("user") .anyRequest().authenticated()) @@ -29,6 +34,7 @@ public class WebSecurityConfig { .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtToken -> { Map> realmAccess = jwtToken.getClaim("realm_access"); Collection roles = realmAccess.get("roles"); + System.out.println("ROLES FROM TOKEN " + roles); List authorities = roles.stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) .toList(); @@ -37,4 +43,18 @@ public class WebSecurityConfig { return http.build(); } + + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowedOrigins(List.of("http://localhost:3000")); + config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "OPTIONS")); + config.setAllowCredentials(true); + config.setAllowedHeaders(List.of("Authorization", "Content-Type")); + UrlBasedCorsConfigurationSource source = + new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return source; + } } diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java index f7d35c1..beb97b4 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Admin.java @@ -14,7 +14,7 @@ import jakarta.persistence.Entity; public class Admin extends User{ public Admin(String id_keycloak, String name, String prenom){ - super(name, id_keycloak, prenom, Role.ADMIN ); + super(name, id_keycloak, prenom, Role.admin ); } @Override diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java index c43b77f..745e329 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Athlete.java @@ -28,7 +28,7 @@ public class Athlete extends User{ private List sessions = new ArrayList<>(); // plusieurs sessions sont possibles public Athlete(String name, String id_keycloak, String prenom){ - super(name, id_keycloak, prenom, Role.ATHLETE); + super(name, id_keycloak, prenom, Role.athlete); } @Override diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java index cd2ade7..0ddb7a7 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Coach.java @@ -20,7 +20,7 @@ public class Coach extends User{ private List sessions = new ArrayList<>(); // Un coach peut avoir plusieurs sessions public Coach(String name, String id_keycloak, String prenom){ - super(name, id_keycloak, prenom, Role.COACH ); + super(name, id_keycloak, prenom, Role.coach ); } @Override diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java index dbdc97e..c80919c 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Role.java @@ -1,7 +1,7 @@ package hackathon.FrisbYEE.jpa.metier; public enum Role { - ADMIN, - COACH, - ATHLETE + admin, + coach, + athlete } \ No newline at end of file diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java index 7711c2a..11273a4 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/metier/Session.java @@ -58,7 +58,7 @@ public class Session { } public void setCoach(Coach coach) { - if (coach.getRole() != Role.COACH) { + if (coach.getRole() != Role.coach) { throw new IllegalArgumentException("L'utilisateur n'est pas un coach"); } this.coach = coach; @@ -66,7 +66,7 @@ public class Session { public void setAthletes(List athletes) { for (Athlete athlete : athletes) { - if (athlete.getRole() != Role.ATHLETE) { + if (athlete.getRole() != Role.athlete) { throw new IllegalArgumentException("L'utilisateur n'est pas un athlète"); } } diff --git a/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java b/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java index b758823..27bf285 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/rest/ActiviteResource.java @@ -20,6 +20,7 @@ import org.springframework.web.bind.annotation.*; import java.util.List; import java.util.stream.Collectors; + @CrossOrigin(origins = "http://localhost:3000") @Controller @RequestMapping("/activite") @@ -49,6 +50,7 @@ public class ActiviteResource { public ResponseEntity create(@RequestBody ActiviteDTO dto) { try { + System.out.println("ROLE TEST " + hackathon.FrisbYEE.jpa.metier.Role.coach); Session session = sessionDAO.findById(dto.getSessionId()).get(); Activite activite = mapToEntity(dto); activite.setSession(session); diff --git a/front_end/public/keycloak.json b/front_end/public/keycloak.json index 119260d..f236670 100644 --- a/front_end/public/keycloak.json +++ b/front_end/public/keycloak.json @@ -1,6 +1,7 @@ { "realm": "Frisbyee_realm", "resource": "Frisbyee_client", + "clientId": "Frisbyee_client", "auth-server-url": "http://localhost:8080", "public-client": true } \ No newline at end of file diff --git a/front_end/src/api.ts b/front_end/src/api.ts index aea29ba..9c8ae21 100644 --- a/front_end/src/api.ts +++ b/front_end/src/api.ts @@ -6,6 +6,7 @@ const api = axios.create({ headers: { "Content-Type": "application/json", }, + withCredentials: true, }); api.interceptors.request.use((config) => { diff --git a/front_end/src/classes.tsx b/front_end/src/classes.tsx index 42d638c..011a8fc 100644 --- a/front_end/src/classes.tsx +++ b/front_end/src/classes.tsx @@ -105,6 +105,7 @@ export function getUserTest():User{ s1.isRecurrent = true; s1.ligne = [ligne1]; s1.duree= 90; + s1.athletes = [athlete1,athlete2]; var date2 = new Date(); date2.setDate(date2.getDate() + 2); s2.creneau = date2; @@ -113,19 +114,15 @@ export function getUserTest():User{ s2.name = "entraintement 2" s2.ligne = [ligne2]; s2.duree= 120; + s2.athletes = [athlete1,athlete2, athlete3]; + s3.creneau = date2; s3.id = 3; s3.isRecurrent = false; s3.name = "entraintement 3" s3.ligne = [ligne3, ligne1]; s3.duree= 120; - - - - s1.athletes = [athlete1, athlete2]; - s2.athletes = [athlete2, athlete3]; - s3.athletes = [athlete1, athlete3]; - + s3.athletes = [athlete2, athlete3]; const act1 = new Activite(); act1.id = 1; diff --git a/front_end/src/components/createSession.tsx b/front_end/src/components/createSession.tsx index d7a10b9..6f385ba 100644 --- a/front_end/src/components/createSession.tsx +++ b/front_end/src/components/createSession.tsx @@ -1,7 +1,7 @@ import { useState, useEffect } from "react"; import { Session, User, Coach, Activite, Groupe } from "../classes"; import { useLocalData } from "../context/useLocalData"; -import { sessionService } from "../api"; +import { activiteService, sessionService } from "../api"; export const CreateSession = () => { const {user} = useLocalData() @@ -24,7 +24,7 @@ export const CreateSession = () => { newActivite.duree= activiteDuree; newActivite.data= new Map(); try{ - await sessionService.create(newActivite); + await activiteService.create(newActivite); console.log("Session créée"); setActivities([...activities, newActivite]); diff --git a/front_end/src/components/login.tsx b/front_end/src/components/login.tsx index c02d1b5..0abca28 100644 --- a/front_end/src/components/login.tsx +++ b/front_end/src/components/login.tsx @@ -59,7 +59,7 @@ export const Login =() =>{ function handleLogin(): void { - keycloak.login() + keycloak.login(); //TODO setUser } @@ -80,6 +80,9 @@ export const Login =() =>{
User nom : { user.nom}
+
+ User role : { user.role} +
} diff --git a/front_end/src/components/ressourceList.tsx b/front_end/src/components/ressourceList.tsx index 0eca026..3788295 100644 --- a/front_end/src/components/ressourceList.tsx +++ b/front_end/src/components/ressourceList.tsx @@ -1,23 +1,82 @@ +import React from "react"; import { Athlete, Activite, Coach, Session, Ligne } from "../classes"; +import {calculStatsAthlete, niveauAlerte} from "../utils/athleteUtils"; import {calculTempsDeJeuParLigne} from "../utils/ligneUtils"; -type AthleteListProps = { athletes: Athlete[] }; +type AthleteListProps = { athletes: Athlete[], sessions: Session[]}; type ActiviteListProps = { activites: Activite[] }; type CoachListProps = { coachs: Coach[] }; type SessionListProps = { sessions: Session[]}; type LigneListProps = { lignes: Ligne[], tempsDeJeuParLigne: Map }; -function AthleteList({ athletes }: AthleteListProps) { +function AthleteList({ athletes, sessions }: AthleteListProps) { + const [dateDebut, setDateDebut] = React.useState(new Date()); + const [dateFin, setDateFin] = React.useState(new Date()); + const [seuilCritique, setSeuilCritique] = React.useState(0); + const [seuilMax, setSeuilMax] = React.useState(0); + + const dateToDatetimeLocal = (date: Date) => { + const pad = (n: number) => n.toString().padStart(2, "0"); + return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(date.getHours())}:${pad(date.getMinutes())}`; + }; + return ( -
    - {athletes.map((athlete) => ( -
  • -
    Nom: {athlete.nom}
    -
    Groupe: {athlete.groupe}
    -
  • - ))} -
+ <> +
+ + + + + +
+ +
    + {athletes.map(a => { + const stats = calculStatsAthlete(sessions, a, dateDebut, dateFin); + const alerte = niveauAlerte(stats, seuilCritique, seuilMax); + + return ( +
  • +
    Nom: {a.nom}
    +
    Groupe: {a.groupe}
    +
    Nombre de sessions: {stats.nbSessions}
    +
    Sessions/semaine: {stats.nbSessionsPerWeek.toFixed(2)}
    +
    Alerte: {alerte}
    +
  • + ); + })} +
+ ); } diff --git a/front_end/src/components/ressourcePanel.tsx b/front_end/src/components/ressourcePanel.tsx index 85cf7c7..ad21358 100644 --- a/front_end/src/components/ressourcePanel.tsx +++ b/front_end/src/components/ressourcePanel.tsx @@ -91,7 +91,7 @@ import { unescapeLeadingUnderscores } from "typescript"; {value==="athletes" && (

Liste des athlètes

- +
)} diff --git a/front_end/src/utils/athleteUtils.tsx b/front_end/src/utils/athleteUtils.tsx new file mode 100644 index 0000000..1a870cb --- /dev/null +++ b/front_end/src/utils/athleteUtils.tsx @@ -0,0 +1,46 @@ +import { Athlete, Session } from '../classes'; + +export interface StatsAthlete { + nbSessions: number; + nbSessionsPerWeek: number; + isAlerte: boolean; + distributions: Map; //le nom de l'activité et son nombre +} + +export function niveauAlerte(stats: StatsAthlete, seuilCritique = 0, seuilMax = 0) { + if (stats.nbSessionsPerWeek > seuilMax) return "Alerte ! Niveau maximal atteint."; + if (stats.nbSessionsPerWeek > seuilCritique) return "Attention! Niveau critique atteint."; + return "Normal"; +} + +export function calculStatsAthlete(sessions: Session[], athlete: Athlete, debut: Date, fin: Date): StatsAthlete { + let nb_sessions = 0; + let nb_semaine = 1; //forcément une semaine + const distributions: Map = new Map(); + const timeDiff = Math.abs(fin.getTime() - debut.getTime()); + nb_semaine = Math.ceil(timeDiff / (1000 * 3600 * 24 * 7)); + + sessions.forEach(session => { + // verification session dans l'intervalle + if (session.creneau < debut || session.creneau > fin) return; + + // verification athlete dans session + if (!session.athletes.some(a => a.id === athlete.id)) return; + + //incrementation (verifie si recurent ou non) + const increment = session.isRecurrent ? nb_semaine : 1; + nb_sessions += increment; + + } + ); + + const nbSessionsPerWeek = nb_sessions / nb_semaine; + const isAlerte = nbSessionsPerWeek > 8; + + return { + nbSessions: nb_sessions, + nbSessionsPerWeek: nbSessionsPerWeek, + isAlerte: isAlerte, + distributions: distributions + }; +} \ No newline at end of file