diff --git a/back_end/src/main/java/hackathon/FrisbYEE/jpa/service/UserDAO.java b/back_end/src/main/java/hackathon/FrisbYEE/jpa/service/UserDAO.java index 5481dbe..d63ee5c 100644 --- a/back_end/src/main/java/hackathon/FrisbYEE/jpa/service/UserDAO.java +++ b/back_end/src/main/java/hackathon/FrisbYEE/jpa/service/UserDAO.java @@ -1,10 +1,14 @@ package hackathon.FrisbYEE.jpa.service; +import hackathon.FrisbYEE.jpa.metier.Coach; import hackathon.FrisbYEE.jpa.metier.User; + +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserDAO extends JpaRepository { - + Optional findByKeycloakId(String keycloakId); } diff --git a/back_end/src/main/java/hackathon/FrisbYEE/rest/UserResource.java b/back_end/src/main/java/hackathon/FrisbYEE/rest/UserResource.java new file mode 100644 index 0000000..0e50e58 --- /dev/null +++ b/back_end/src/main/java/hackathon/FrisbYEE/rest/UserResource.java @@ -0,0 +1,71 @@ +package hackathon.FrisbYEE.rest; + +import java.util.ArrayList; +import java.util.List; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.CrossOrigin; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import hackathon.FrisbYEE.jpa.dto.AthleteDTO; +import hackathon.FrisbYEE.jpa.dto.UserDTO; +import hackathon.FrisbYEE.jpa.metier.Athlete; +import hackathon.FrisbYEE.jpa.metier.User; +import hackathon.FrisbYEE.jpa.service.UserDAO; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.parameters.RequestBody; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@RestController +@RequestMapping("/users") +@CrossOrigin(origins = "http://localhost:3000") +public class UserResource { + @Autowired + private UserDAO userDAO; + + @Operation(summary = "Récupère tous les utilisateurs") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Récupère tous les athlètes", content = @Content(mediaType = "application/json", schema = @Schema(implementation = List.class))) + }) + @GetMapping("/all") + @PreAuthorize("hasRole('admin') or hasRole('coach')") + public ResponseEntity> all() { + List users = userDAO.findAll(); + List dtos = new ArrayList<>(); + for (User user : users) { + dtos.add(mapToDTO(user)); + } + return ResponseEntity.ok(dtos); + } + + @Operation(summary = "Récupère l'utilisateur ayant l'identifiant correspondant") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Récupération effectuée", content = @Content(mediaType = "application/json", schema = @Schema(implementation = UserDTO.class))) + }) + @GetMapping("/{id}") + @PreAuthorize("hasRole('admin') or hasRole('coach') or hasRole('athlete')") + public ResponseEntity getById(@PathVariable String id) { + User user = userDAO.findByKeycloakId(id).get(); + return ResponseEntity.ok(mapToDTO(user)); + } + + private UserDTO mapToDTO(User user) { + UserDTO dto = new UserDTO(); + dto.setId_keycloak(user.getKeycloakId()); + dto.setName(user.getName()); + dto.setPrenom(user.getPrenom()); + dto.setRole(user.getRole()); + + return dto; + } +} \ No newline at end of file diff --git a/front_end/src/api.ts b/front_end/src/api.ts index 9c8ae21..4d68e7b 100644 --- a/front_end/src/api.ts +++ b/front_end/src/api.ts @@ -1,27 +1,36 @@ import axios from "axios"; -import keycloak from "./keycloak"; const api = axios.create({ - baseURL: "http://localhost:8081/api", + // backend listens on 8081 and controllers are mounted at root (no /api prefix) + baseURL: "http://localhost:8081", headers: { "Content-Type": "application/json", }, - withCredentials: true, }); +// Simple interceptor to ensure headers object exists; actual token should be set via setAuthToken() api.interceptors.request.use((config) => { - if (keycloak?.token) { - // eslint-disable-next-line no-param-reassign - config.headers.Authorization = `Bearer ${keycloak.token}`; - console.log(config.headers.Authorization); - } + if (!config.headers) config.headers = {}; return config; }); +// Helpers to set/clear the Authorization header programmatically (call after Keycloak login) +export function setAuthToken(token: string | null) { + if (token) { + api.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } else { + delete api.defaults.headers.common["Authorization"]; + } +} + +export function clearAuthToken() { + delete api.defaults.headers.common["Authorization"]; +} + export const athleteService = { create: (data: any) => api.post("/athletes/create", data), getAll: () => api.get("/athletes/all"), - getById: (id: number | string) => api.get(`/athletes/${id}`), + getByKeycloakId: (id: number | string) => api.get(`/athletes/${id}`), update: (id: number | string, data: any) => api.put(`/athletes/${id}`, data), delete: (id: number | string) => api.delete(`/athletes/${id}`), @@ -63,7 +72,7 @@ export const coachService = { // controller doesn't declare a class-level path consistently; support both common patterns create: (data: any) => api.post(`/coach/create`, data), getAll: () => api.get(`/coach/all`), - getById: (id: number | string) => api.get(`/coach/${id}`), + getByKeycloakId: (id: number | string) => api.get(`/coach/${id}`), update: (id: number | string, data: any) => api.put(`/coach/update/${id}`, data), delete: (id: number | string) => api.delete(`/coach/delete/${id}`), @@ -73,7 +82,7 @@ export const coachService = { }; export const userService = { - getById: (id: number | string) => api.get(`/users/${id}`), + getByKeycloakId: (id: number | string) => api.get(`/users/${id}`), getAll: () => api.get(`/users`), }; diff --git a/front_end/src/components/login.tsx b/front_end/src/components/login.tsx index 11e706f..cd30877 100644 --- a/front_end/src/components/login.tsx +++ b/front_end/src/components/login.tsx @@ -2,6 +2,7 @@ import { useKeycloak } from '@react-keycloak/web' import { useEffect } from 'react'; import { getUserTest, User } from '../classes'; import { useLocalData } from '../context/useLocalData'; +import { setAuthToken, clearAuthToken } from '../api'; export const Login =() =>{ const {user,setUser} = useLocalData() @@ -11,6 +12,8 @@ export const Login =() =>{ const syncAndLoadUser = async () => { if (keycloak.authenticated && keycloak.token) { const tokenParsed = keycloak.tokenParsed; + // set axios default auth header for API calls + setAuthToken(keycloak.token); setUser({ id: 0, keycloakId: tokenParsed!.sub!, @@ -47,6 +50,7 @@ export const Login =() =>{ function handleLogout(): void { keycloak.logout() setUser(new User()); + clearAuthToken(); } return(