This commit is contained in:
trochas
2026-01-06 15:13:41 +01:00
17 changed files with 2088 additions and 342 deletions

View File

@@ -1,6 +1,8 @@
package hackathon.FrisbYEE.jpa.dto;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data

View File

@@ -1,9 +0,0 @@
package hackathon.FrisbYEE.jpa.interfaces;
import java.io.Serializable;
public interface IAthlete{
public void run();
}

View File

@@ -4,9 +4,8 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToOne;
import lombok.Getter;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
import jakarta.persistence.Access;

View File

@@ -3,8 +3,11 @@ package hackathon.FrisbYEE.jpa.service;
import hackathon.FrisbYEE.jpa.metier.Activite;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface ActiviteDAO extends JpaRepository<Activite, Integer> {
List<Activite> findByTheme(String theme);
}

View File

@@ -84,7 +84,8 @@ public class ActiviteResource {
}
@GetMapping("/read/{id}")
@GetMapping("/{id}")
@PreAuthorize("hasRole('Coach') or hasRole('Athlete')")
@ResponseBody
public ResponseEntity<ActiviteDTO> getActivityById(@PathVariable("id") int id) {
try {
@@ -104,6 +105,7 @@ public class ActiviteResource {
}
@GetMapping("/all")
@PreAuthorize("hasRole('Coach') or hasRole('Athlete')")
@ResponseBody
public ResponseEntity<List<ActiviteDTO>> getAllActivity() {
try {
@@ -124,4 +126,28 @@ public class ActiviteResource {
}
}
@GetMapping("/theme/{theme}")
@PreAuthorize("hasRole('Coach') or hasRole('Athlete')")
@ResponseBody
public ResponseEntity<List<ActiviteDTO>> getActivityByTheme(@PathVariable("theme") String theme) {
try {
List<Activite> activites = activiteDAO.findByTheme(theme);
List<ActiviteDTO> dtos = activites.stream().map(activite -> {
ActiviteDTO dto = new ActiviteDTO();
dto.setName(activite.getName());
dto.setId(activite.getId());
dto.setTheme(activite.getTheme());
dto.setDuree(activite.getDuree());
dto.setDataActivite(activite.getDataActivite());
dto.setSessionId(activite.getSession() != null ? activite.getSession().getId() : null);
return dto;
}).collect(Collectors.toList());
return ResponseEntity.ok(dtos);
} catch (Exception ex) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
}
}

View File

@@ -1,35 +1,56 @@
package hackathon.FrisbYEE.rest;
import org.apache.el.stream.Optional;
import java.time.LocalDate;
import java.time.chrono.ChronoLocalDateTime;
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.DeleteMapping;
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.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestMapping;
import hackathon.FrisbYEE.jpa.dto.ActiviteDTO;
import hackathon.FrisbYEE.jpa.dto.AthleteDTO;
import hackathon.FrisbYEE.jpa.dto.SessionDTO;
import hackathon.FrisbYEE.jpa.metier.Activite;
import hackathon.FrisbYEE.jpa.metier.Athlete;
import hackathon.FrisbYEE.jpa.metier.Session;
import hackathon.FrisbYEE.jpa.service.AthleteDAO;
import hackathon.FrisbYEE.jpa.service.SessionDAO;
@RestController
@RequestMapping("/athletes")
public class AthleteResource {
@Autowired
private AthleteDAO athleteDAO;
private SessionDAO sessionDAO;
@PostMapping("/create")
@PreAuthorize("hasRole('Admin')") // Only admin can create??
public ResponseEntity<AthleteDTO> create(@RequestBody AthleteDTO dto) {
Athlete ahtlete = new Athlete();
athlete.setName(dto.getName())
athlete.setCategorie(dto.getCategorie())
athlete.setNiveau(dto.getNiveau())
Athlete athlete = new Athlete();
athlete.setName(dto.getName());
athlete.setCategorie(dto.getCategorie());
athlete.setNiveau(dto.getNiveau());
return ResponseEntity.status(HttpStatus.CREATED).body(mapToDTO(athlete));
}
@PostMapping("/all")
@PreAuthorize("hasRole('Admin') or hasRole('Coach') or hasRole('Athlete')")
public ReponseEntity<List<AthleteDTO>> all() {
public ResponseEntity<List<AthleteDTO>> all() {
List<Athlete> athletes = athleteDAO.findAll();
List<AthleteDTO> dtos = new ArrayList<>();
for (Athlete athlete : athletes) {
dtos.add(maptoDTO(athlete));
dtos.add(mapToDTO(athlete));
}
return ResponseEntity.ok(dtos);
}
@@ -51,20 +72,15 @@ public class AthleteResource {
athlete.setCategorie(dto.getCategorie());
athlete.setNiveau(dto.getNiveau());
// Optional
athlete.setDuree(dto.getDuree());
athlete.setTheme(dto.getTheme());
// List
if (dto.getDataActivite() != null) {
athlete.setDataActivite(dto.getDataActivite());
}
// Relationship: sessionId → session
if (dto.getSessionId() != null) {
Session session = sessionDAO.findById(dto.getSessionId())
.orElseThrow(() -> new RuntimeException("Session not found"));
athlete.setSession(session);
if (dto.getSessionIds() != null) {
List<Session> sessions = new ArrayList<>();
for (Integer sessionId : dto.getSessionIds()) {
Session session = sessionDAO.findById(sessionId)
.orElseThrow(() -> new RuntimeException("Session not found"));
sessions.add(session);
}
athlete.setSessions(sessions);
}
athleteDAO.save(athlete);
@@ -92,4 +108,104 @@ public class AthleteResource {
dto.setNiveau(athlete.getNiveau());
return dto;
}
@GetMapping("/athlete/{id}/session")
public List<SessionDTO> getSessionsAthlete(@PathVariable Integer athleteId) {
// return pet
System.out.println("ID A CHERCHER" + athleteId);
java.util.Optional<Athlete> j = athleteDAO.findById(athleteId);
List<Session> sessions = sessionDAO.findAll();
List<SessionDTO> athleteSessions = new ArrayList<>();
for(Session s : sessions){
if(s.getAthletes().contains(j.get())){
SessionDTO dto = new SessionDTO();
dto.setId(s.getId());
dto.setName(s.getName());
// Map other fields as necessary
athleteSessions.add(dto);
}
}
System.out.println(j);
return athleteSessions;
}
@GetMapping("/athletes/session")
public List<SessionDTO> getAllSessions() {
List<Session> sessions = sessionDAO.findAll();
System.out.println(sessions);
List<SessionDTO> sessionDTOs = new ArrayList<>();
for (Session session : sessions) {
SessionDTO dto = new SessionDTO();
dto.setId(session.getId());
dto.setName(session.getName());
// Map other fields as necessary
sessionDTOs.add(dto);
}
return sessionDTOs;
}
@GetMapping("/athletes/session/{id}/activities")
public List<ActiviteDTO> getActivitiesForSession(@PathVariable Integer id) {
// Récupérer la session par ID
java.util.Optional<Session> sessionOpt = sessionDAO.findById(id);
if (sessionOpt.isPresent()) {
Session session = sessionOpt.get();
// Retourner les activités de la session
List<ActiviteDTO> activiteDTOs = new ArrayList<>();
for (Activite activite : session.getActivites()) {
ActiviteDTO dto = new ActiviteDTO();
dto.setId(activite.getId());
dto.setName(activite.getName());
// Map other fields as necessary
activiteDTOs.add(dto);
}
return activiteDTOs;
}
return new ArrayList<>();
}
@GetMapping("/athletes/{id}/session/after/{date}")
public List<SessionDTO> getSessionsAfterDate(@PathVariable Integer id, @PathVariable String date) {
// Récupérer l'athlète par ID
java.util.Optional<Athlete> athleteOpt = athleteDAO.findById(id);
if (athleteOpt.isPresent()) {
Athlete athlete = athleteOpt.get();
// Récupérer les sessions de l'athlète après la date donnée
List<Session> sessions = sessionDAO.findAll();
List<SessionDTO> filteredSessions = new ArrayList<>();
for (Session session : sessions) {
if (session.getAthletes().contains(athlete) && session.getCreneau().isAfter(ChronoLocalDateTime.from(LocalDate.parse(date)))) { //WTF toujours sympa les dates
SessionDTO dto = new SessionDTO();
dto.setId(session.getId());
dto.setName(session.getName());
// Map other fields as necessary
filteredSessions.add(dto);
}
}
return filteredSessions;
}
return new ArrayList<>();
}
@GetMapping("/athletes/{id}/session/between/{startDate}/{endDate}")
public List<SessionDTO> getSessionsBetweenDates(@PathVariable Integer id, @PathVariable String startDate, @PathVariable String endDate) {
// Récupérer l'athlète par ID
java.util.Optional<Athlete> athleteOpt = athleteDAO.findById(id);
if (athleteOpt.isPresent()) {
Athlete athlete = athleteOpt.get();
// Récupérer les sessions de l'athlète entre les deux dates données
List<Session> sessions = sessionDAO.findAll();
List<SessionDTO> filteredSessions = new ArrayList<>();
for (Session session : sessions) {
if (session.getAthletes().contains(athlete) && session.getCreneau().isAfter(ChronoLocalDateTime.from(LocalDate.parse(startDate))) && session.getCreneau().isBefore(ChronoLocalDateTime.from(LocalDate.parse(endDate)))) {
SessionDTO dto = new SessionDTO();
dto.setId(session.getId());
dto.setName(session.getName());
// Map other fields as necessary
filteredSessions.add(dto);
}
}
return filteredSessions;
}
return new ArrayList<>();
}
}

View File

@@ -27,20 +27,3 @@ services:
interval: 5s
timeout: 5s
retries: 20
postgres:
image: postgres:16
container_name: frisbyee-postgres
ports:
- "5432:5432"
environment:
POSTGRES_DB: frisbyee
POSTGRES_USER: frisbyee_user
POSTGRES_PASSWORD: secret
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
version: "3.9"

File diff suppressed because it is too large Load Diff

View File

@@ -12,6 +12,8 @@
"@types/node": "^16.18.126",
"@types/react": "^19.2.7",
"@types/react-dom": "^19.2.3",
"axios": "^1.13.2",
"keycloak-js": "^26.2.2",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-scripts": "5.0.1",

View File

@@ -6,6 +6,7 @@ import Login from './components/login';
import { LocalDataProvider } from './provider/LocalDataProvider';
import ENT from './components/ent';
import SwitchThemeColor from './components/SwitchThemeColor';
import CreateSession from './components/createSession'
const keycloakInitOptions = {
@@ -22,6 +23,7 @@ function App() {
<h1>Frisbyee</h1>
<Login/>
<ENT/>
<CreateSession/>
</div>
</LocalDataProvider>
</ReactKeycloakProvider>

15
front_end/src/api.ts Normal file
View File

@@ -0,0 +1,15 @@
import axios from "axios";
import keycloak from "./keycloak";
const api = axios.create({
baseURL: "http://localhost:8081/api",
});
api.interceptors.request.use((config) => {
if (keycloak?.token) {
config.headers.Authorization = `Bearer ${keycloak.token}`;
}
return config;
});
export default api;

View File

@@ -1,4 +1,4 @@
export type Groupe = "Entrainement" | "Competition" | "Loisir";
export type Groupe = "Entrainement" | "Competition" | "Loisir"| "";
export class User{
id!: number;
@@ -21,6 +21,7 @@ export class Coach extends User{
export class Session{
id!: number;
name!: String;
activites!: Activite[];
isRecurent! : Boolean;
creneau!: Date;

View File

@@ -0,0 +1,106 @@
import { useState, useEffect } from "react";
import { Session, User, Coach, Activite, Groupe } from "../classes";
import { useLocalData } from "../context/useLocalData";
export const CreateSession = () => {
const {user} = useLocalData()
const [name,setName] = useState("");
const [groupe, setGroupe] = useState<Groupe>("");
const [creneau, setCreneau] = useState("");
const [duree, setDuree] = useState<number>(0);
const [activities, setActivities] = useState<Activite[]>([]);
const [activiteNom, setActiviteNom] = useState("");
const [activiteTheme, setActiviteTheme] = useState("");
const [activiteDuree, setActiviteDuree] = useState(0);
const [isRecurent, setIsRecurent] = useState(false);
function addAcitivte(){
if (!activiteNom) return;
const newActivite = new Activite();
newActivite.nom= activiteNom;
newActivite.theme=activiteTheme;
newActivite.duree= activiteDuree;
newActivite.data= new Map<String,String>();
setActivities([...activities, newActivite]);
setActiviteNom("");
setActiviteTheme("");
setActiviteDuree(0);
}
async function handleCreateSession(){
const newSession = new Session();
newSession.name = name;
newSession.activites= activities;
newSession.groupe = groupe;
newSession.creneau = new Date(creneau);
newSession.duree= duree;
newSession.isRecurent= isRecurent;
newSession.coach= user as Coach;
newSession.athletes= [];
newSession.activites= activities;
try{
//reset
setName("");
setGroupe("");
setCreneau("");
setDuree(0);
setIsRecurent(false);
setActivities([]);
}catch (err){
console.error(err)
}
}
return (
<div className="ent">
<h2>Activité</h2>
<label>
Name:
<input type="text" value={activiteNom} onChange={e => setActiviteNom(e.target.value)} />
</label>
<label>
Theme:
<input type="text" value={activiteTheme} onChange={e => setActiviteTheme(e.target.value)} />
</label>
<label>
Duree (minutes):
<input type="number" value={activiteDuree} onChange={e => setActiviteDuree(Number(e.target.value))} />
</label>
<button type="button" onClick={addAcitivte}>Add Activite</button>
<ul>
{activities.map((act, idx) => (
<li key={idx}>{act.nom} - {act.theme} ({act.duree} min)</li>
))}
</ul>
<h2>Create Session</h2>
<label>
Name:
<input type="text" value={name} onChange={e => setName(e.target.value)} />
</label>
<label>
Groupe:
</label>
<label>
Creneau:
<input type="datetime-local" value={creneau} onChange={e => setCreneau(e.target.value)} />
</label>
<label>
Duree (minutes):
<input type="number" value={duree} onChange={e => setDuree(Number(e.target.value))} />
</label>
<label>
Recurrent:
<input type="checkbox" checked={isRecurent} onChange={e => setIsRecurent(e.target.checked)} />
</label>
<button type="button" onClick={handleCreateSession}>Create Session</button>
</div>
);
};
export default CreateSession;

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from "react"
import { getUserTest, Session, User } from "../classes"
import { useLocalData } from "../context/useLocalData"
import './style/ent.css';
import {updateSessionsOfUser } from "../requetes";
import {updateSessionsOfUserAPI } from "../requetes";
export const ENT =() =>{
const {user,setUser} = useLocalData()

View File

@@ -1,3 +1,4 @@
import api from "./api";
import { Activite, Athlete, Coach, Session, User } from "./classes";
import { useKeycloak } from '@react-keycloak/web'
@@ -16,50 +17,95 @@ const useAuthHeader = () => {
/*
retourne l'utilisateur lié à l'identifiant keyloack
*/
export async function getUser(id:number): Promise<boolean>{
//keycloak.id;
return true;
export async function getUser(id:number): Promise<User|null>{
try {
const response = await api.get<User>(`/users/${id}`);
return response.data;
}
catch (error) {
console.error("Error fetching user:", error);
return null;
}
}
/*
retourne toutes les Session dont l'user est inscrit
*/
export async function updateSessionsOfUser(user:Coach|Athlete, min: Date|null, max: Date|null){
//TODO
export async function updateSessionsOfUserAPI(user:Coach|Athlete, min: Date|null, max: Date|null){
try {
const response = await api.get<Session[]>(`/users/${user.id}/sessions`, {
params: {
minDate: min ? min.toISOString() : undefined,
maxDate: max ? max.toISOString() : undefined
}
});
return response.data;
} catch (error) {
console.error("Error fetching sessions for user:", error);
return [];
}
}
export async function updateActivitiesOfSession(session:Session){
//TODO
export async function updateActivitiesOfSessionAPI(session:Session){
try {
const response = await api.get<Activite[]>(`/sessions/${session.id}/activities`);
return response.data;
} catch (error) {
console.error("Error fetching activities for session:", error);
return [];
}
}
export async function subscribeSession(user:User, session:Session):Promise<boolean>{
return true;
export async function subscribeSessionAPI(user:User, session:Session):Promise<boolean>{
try {
await api.post(`/sessions/${session.id}/subscribe`, { userId: user.id });
return true;
} catch (error) {
console.error("Error subscribing to session:", error);
return false;
}
}
export async function unsubscribeSession(user:User, session:Session):Promise<boolean>{
return true;
export async function unsubscribeSessionAPI(user:User, session:Session):Promise<boolean>{
try {
await api.post(`/sessions/${session.id}/unsubscribe`, { userId: user.id });
return true;
} catch (error) {
console.error("Error unsubscribing from session:", error);
return false;
}
}
// ADMIN :
export async function updateAllSession(min: Date, max: Date){
export async function updateAllSessionAPI(min: Date, max: Date){
//TODO
}
export async function updateAllUser(){
export async function updateAllUserAPI(){
}
// POST /////////////////////////////////////////////////////////
// COACH ADMIN
export async function postSession(session: Session){
export async function createSessionAPI(newSession: Session): Promise<Session> {
try {
const response = await api.post<Session>("/sessions", newSession);
return response.data;
} catch (error) {
console.error("Error creating session:", error);
throw error;
}
}
export async function postActivity(session: Session, activity: Activite){
//post nouvelle activitée
//associer la nouvelle activité à la session
export async function postActivityAPI(session: Session, activity: Activite){
try {
const response = await api.post<Activite>(`/sessions/${session.id}/activities`, activity);
return response.data;
} catch (error) {
console.error("Error creating activity:", error);
throw error;
}
}
export async function postUser(user: User):Promise<boolean>{
@@ -69,11 +115,30 @@ export async function postUser(user: User):Promise<boolean>{
// SET /////////////////////////////////////////////////////////
//ADMIN
export async function setUserName(user: User, name: string){
export async function setUserNameAPI(user: User, name: string){
}
//COACH
export async function setSessionCreneau(session: Session, date:Date){
export async function setSessionCreneauAPI(session: Session, date:Date){
}
export async function getSessionsAPI(): Promise<Session[]> {
try {
const response = await api.get<Session[]>("/sessions");
return response.data;
} catch (error) {
console.error("Error fetching sessions:", error);
throw error;
}
}
export async function getUsersAPI(): Promise<User[]> {
try {
const response = await api.get<User[]>("/users");
return response.data;
} catch (error) {
console.error("Error fetching users:", error);
throw error;
}
}

View File

@@ -1,11 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"target": "ES2020",
"lib": ["DOM", "DOM.Iterable", "ES2020"],
"module": "ESNext",
"moduleResolution": "bundler",
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
@@ -13,14 +12,10 @@
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
"include": ["src"]
}

94
package-lock.json generated
View File

@@ -2,5 +2,97 @@
"name": "hackathon",
"lockfileVersion": 3,
"requires": true,
"packages": {}
"packages": {
"": {
"dependencies": {
"react-router-dom": "^7.11.0"
}
},
"node_modules/cookie": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/react": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
"integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==",
"license": "MIT",
"peer": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-dom": {
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.3"
}
},
"node_modules/react-router": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.11.0.tgz",
"integrity": "sha512-uI4JkMmjbWCZc01WVP2cH7ZfSzH91JAZUDd7/nIprDgWxBV1TkkmLToFh7EbMTcMak8URFRa2YoBL/W8GWnCTQ==",
"license": "MIT",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
}
}
},
"node_modules/react-router-dom": {
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.11.0.tgz",
"integrity": "sha512-e49Ir/kMGRzFOOrYQBdoitq3ULigw4lKbAyKusnvtDu2t4dBX4AGYPrzNvorXmVuOyeakai6FUPW5MmibvVG8g==",
"license": "MIT",
"dependencies": {
"react-router": "7.11.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
"react": ">=18",
"react-dom": ">=18"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/set-cookie-parser": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
"license": "MIT"
}
}
}