diff --git a/.idea/misc.xml b/.idea/misc.xml index 639900d..a7e1300 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,9 @@ + + + + diff --git a/.idea/modules.xml b/.idea/modules.xml index 2db4f39..6ad2b6b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -2,7 +2,6 @@ - diff --git a/FencerJudgeBack/README.md b/FencerJudgeBack/README.md new file mode 100644 index 0000000..e334d69 --- /dev/null +++ b/FencerJudgeBack/README.md @@ -0,0 +1,137 @@ +# Entités +## MATCHES (MatchBean) +- id -> Long +- country -> String +- city -> String +- weapon -> String +- player1ID -> Long +- player2ID -> Long +- refereeID (arbitre) -> Long +- score1 (score du joueur 1) -> Integer +- score2 (score du joueur 2) -> Integer +- date -> Date +- state (état du match : en cours, terminé, pas commencé) -> Integer (ENUM) + +## ETATS D'UN MATCH (MatchesStates) +- ONGOING -> Integer (1) +- OVER -> Integer (2) +- NOT STARTED -> Integer (3) + +## ARBITRES (RefereeBean) +- id -> Long +- name (nom de famille) -> String +- firstName (prénom) -> String +- qualification -> String + +## JOUEURS (PlayerBean) +- id -> Long +- name (nom de famille) -> String +- firstName (prénom) -> String +- club -> String + +# Actions REST sur les entités +## matches (MatchRestController, "/matches") +- Lister tous les matches -> **GET "/matches/"** -> List +- Lister tous les matches à partir d'une certaine date -> **GET "/matches/date=&date"** -> List +- Lister les matches en cours (état : en cours) -> **GET "/matches/active"** -> List +- Lister les matches terminés (état : terminé) -> **GET "/matches/over"** -> List +- Lister les matches non commencés (état : non commencé) -> **GET "/matches/not-started"** -> List +- Afficher un match par id -> **GET "/matches/id=&id"** -> MatchBean +- Mettre à jour les score d'un match récupéré par id -> **POST "/matches/update-match"** +{ + id=$id, + score1=$score1, + score2=$score2 +} +-> MatchBean +- Ajouter un match -> **POST "/matches/add-match"** +{ + date=$date, + country=$country + city=$city, + weapon=$weapon, + refereeID=$refereeID, + player1ID=$player1ID, + player2ID=$player2ID, + +} +-> MatchBean +- Supprimer un match (supprimer élément de MatchRepository en récupérant l'id) -> **POST "/matches/delete-match"** +{ + id=$id +} + +## ARBITRES (RefereeRestController, "/referees") : +- Lister tous les arbitres -> **GET "/referees/"** -> List +- Afficher un arbitre par id -> **GET "/referees/id=$id"** -> RefereeBean +- Afficher un arbitre par nom ->**GET "/referees/name=$name"** -> RefereeBean +- Ajouter un arbitre ->**POST "/referees/add-referee"** +{ + name=$name, + firstName=$firstName, + qualification=$qualification +} +-> RefereeBean +- Supprimer un arbitre ->**POST "/referees/delete-referee/"** +{ + id=$id +} + +## JOUEURS (PlayerRestController, "/players") : +- Lister tous les joueurs ->**GET "/players/"** -> List +- Afficher un joueur par id ->**GET "/players/id=$id"** -> PlayerBean +- Afficher un joueur par nom ->**GET "/players/name=$name"** -> PlayerBean +- Ajouter un joueur ->**POST "/players/add-player"** +{ + name=$name, + firstName=$firstName, + club=$club +} +-> PlayerBean +- Supprimer un joueur ->**POST "/players/delete-player"** +{ + id=$id +} + +# Websocket (http://localhost:8080/ws) +- Vider le channel +- Insérer une donnée dans le channel +- Modifier une donnée dans le channel +- Supprimer une donnée du channel + +## MATCHES +## matches (MatchRestController, "/matches") +- Lister tous les matches -> dans le channel Websocket +- Lister tous les matches à partir d'une certaine date -> dans le channel Websocket +- Lister les matches en cours (état : en cours) -> **GET "/matches/active"** -> List +- Lister les matches terminés (état : terminé) -> **GET "/matches/over"** -> List +- Lister les matches non commencés (état : non commencé) -> **GET "/matches/not-started"** -> List +- Afficher un match par id -> **GET "/matches/id=&id"** -> MatchBean +- Mettre à jour les score d'un match récupéré par id -> **POST "/matches/update-match"** +{ + id=$id, + score1=$score1, + score2=$score2 +} +-> MatchBean +- Ajouter un match -> **POST "/matches/add-match"** +{ + date=$date, + country=$country + city=$city, + weapon=$weapon, + refereeID=$refereeID, + player1ID=$player1ID, + player2ID=$player2ID, + +} +-> MatchBean +- Supprimer un match (supprimer élément de MatchRepository en récupérant l'id) -> **POST "/matches/delete-match"** +{ + id=$id +} + +## REFEREES + + +## PLAYERS \ No newline at end of file diff --git a/FencerJudgeBack/TODO.txt b/FencerJudgeBack/TODO.txt new file mode 100644 index 0000000..b3e8f97 --- /dev/null +++ b/FencerJudgeBack/TODO.txt @@ -0,0 +1,6 @@ +corriger les services - V +tester les restcontrollers via des requêtes http - V +créer la bdd avec toutes les tables (repositories) - V +tester le websocket - +prévoir l'utilisation en admin uniquement (spring security) +faire le jackarta de l'api \ No newline at end of file diff --git a/FencerJudgeBack/build.gradle.kts b/FencerJudgeBack/build.gradle.kts index 729b120..f64df6d 100644 --- a/FencerJudgeBack/build.gradle.kts +++ b/FencerJudgeBack/build.gradle.kts @@ -22,10 +22,21 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") + implementation("org.springframework.boot:spring-boot-starter-data-jpa") developmentOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testRuntimeOnly("org.junit.platform:junit-platform-launcher") + implementation("org.springframework.boot:spring-boot-starter-validation") + + // WEBSOCKET + implementation("org.springframework.boot:spring-boot-starter-websocket") + + // SECURITY +// implementation("org.springframework.boot:spring-boot-starter-security") + + // H2 Database + runtimeOnly("com.h2database:h2") } kotlin { diff --git a/FencerJudgeBack/conception.txt b/FencerJudgeBack/conception.txt new file mode 100644 index 0000000..02a822b --- /dev/null +++ b/FencerJudgeBack/conception.txt @@ -0,0 +1,269 @@ +## Websocket +// Ouvrir channel websocket sur port 8500 --> DONE +function startWebsocket(var integer port) -> Websocket: + var Websocket websocket = start Websocket at port [port] + return websocket + +## Entités +MATCHS -> MatchBean, MatchRepository +MatchBean: + - id:Long + - player1:PlayerBean + - player2:PlayerBean + - referee:RefereeBean + - score1:Int (score du player1) + - score2:Int (score du player2) + - date:String (date du match) + - state:String (état du match : terminé, en cours, pas commencé) + +REFEREES (arbitres) -> RefereeBean, RefereeRepository +RefereeBean: + - id:Long + - name:Long (nom) + - firstName:String (prénom) + +PLAYERS -> PlayerBean, PlayerRepository +PlayerBean: + - id:Long + - name:String (nom) + - firstName:String (prénom) + +## Services +MatchService --> DONE: + // Obtenir tous les matchs (public) --> DONE + function getAll() -> array : + return MatchRepository.getAll() + + // Obtenir un match par id (public) --> DONE + function getById(var long id) -> MatchBean : + return MatchRepository.findById(id) + + // Obtenir un ou plusieurs match(s) par joueurs (id) (public) --> DONE + function getByPlayers(var long player1ID, var long player2ID) -> array { + return MatchRepository.getAll().filterBy(player1ID or player2ID) + } + + // Ajouter un match (admin) --> DONE + function createMatch(var MatchBean newMatch) -> MatchBean : + MatchRepository.add(newMatch) + return newMatch + + // Modifier un match (admin) --> DONE + function updateMatch(var long id, var Date date, var long refereeID, var long player1ID, var long player2ID, var integer scorePlayer1, var integer scorePlayer2, var Date date, var string matchState) -> integr (success or not): + // Vérifier si le match existe à l'id renseigné + if MatchRepository.getById(id) doesn't exist { + return FAILED + } + + // Créer nouveau MatchBean à l'id renseigné + var MatchBean newMatch = new MatchBean(id, player1ID, player2ID, refereeID, score1, score2, date, state) + + // Supprimer le MatchBean à l'id en paramètre + MatchRepository.deleteById(id) + + // Insérer le nouveau MatchBean à l'id en paramètre + MatchRepository.add(newMatch) + + return SUCCESS + + + // Supprimer un match (admin) --> DONE + function deleteMatch(var long id) -> integer (success or failure) : + try: + MatchRepository.deleteById(id) + catch error: + return FAILED + + return SUCCESS + +RefereeService --> DONE: + // Obtenir tous les arbitres (public) --> DONE + function getAll() -> array : + return RefereeRepository.getAll() + + // Obtenir un arbitre par id (public) --> DONE + function getById(var long id) -> RefereeBean : + return RefereeRepository.findById(id) + + // Obtenir un ou plusieurs arbitre(s) par nom (public) --> DONE + function getByName(var string name) -> array : + return RefereeRepository.getAll().filterBy(name) + + // Obtenir un ou plusieurs arbitre(s) par prénom (public) + function getByFirstName(var string firstName) -> array : + return RefereeRepository.getAll().filterBy(firstName) + + // Ajouter un arbitre (admin) --> DONE + function createReferee(var RefereeBean newReferee) -> RefereeBean: + RefereeRepository.add(newReferee) + return newReferee + + // Modifier un arbitre (admin) --> DONE + function updateReferee(var long id, var string name, var string firstName) -> integer (success or not): + + // Vérifier si l'arbitre existe à l'id renseigné + if RefereeRepository.getById(id) doesn't exist { + return FAILED + } + + // Créer nouveau RefereeBean à l'id renseigné + var RefereeBean newReferee = new RefereeBean(id, name, firstName) + + // Supprimer le RefereeBean à l'id en paramètre + RefereeRepository.deleteById(id) + + // Insérer le nouveau RefereeBean à l'id en paramètre + RefereeRepository.add(newReferee) + + return SUCCESS + + + // Supprimer un arbitre (admin) --> DONE + function delete(var long id) -> integer (success or failure) : + try: + RefereeRepository.deleteById(id) + catch error: + return FAILED + + return SUCCESS + +PlayerService --> DONE: + // Obtenir tous les joueurs (public) --> DONE + function getAll() -> array : + return PlayerRepository.getAll() + + // Obtenir un joueur par id (public) --> DONE + function getById(var long id) -> PlayerBean : + return PlayerRepository.findById(id) + + // Obtenir un ou plusieurs joueur(s) par nom (public) --> DONE + function getByName(var string name) -> array : + return PlayerRepository.getAll().filterBy(name) + + // Obtenir un ou plusieurs joueur(s) par prénom (public) --> DONE + function getByFirstName(var string firstName) -> array : + return PlayerRepository.getAll().filterBy(firstName) + + // Ajouter un joueur (admin) --> DONE + function add(var PlayerBean newPlayer) -> PlayerBean: + PlayerRepository.add(newPlayer) + return newPlayer + + // Modifier un joueur (admin) --> DONE + function update(var long id, var string name, var string firstName) -> PlayerBean : + // Créer nouveau PlayerBean + var PlayerBean newPlayer = new PlayerBean(id, name, firstName) + + // Supprimer le PlayerBean à l'id en paramètre + deleteById(id) + + // Insérer le nouveau PlayerBean à l'id en paramètre + PlayerRepository.add(newPlayer) + + return newPlayer + + // Supprimer un joueur (admin) --> DONE + function delete(var long id) -> integer (success or failure) : + try: + PlayerRepository.deleteById(id) + catch error: + return FAILED + + return SUCCESS + +## RestControllers +MatchRestController --> TODO: + // Lister tous les matchs --> DONE + function getAll() -> array : + return MatchService.getAll() + + // Lister tous les matchs à partir d'une certaine date + function getAllFromDate(var Date dateToFilter) -> array : + return MatchService.getAll().filter(date == dateToFilter) + + // Lister les matchs en cours (état : en cours) + function getAllActive() -> array : + return MatchService.getAll().filter(state == "ONGOING") + + // Lister les matchs terminés (état : terminé) + function getAllOver() -> array : + return MatchService.getAll().filter(state == "FINISHED") + + // Lister les matchs non commencés (état : non commencé) + function getAllNotStarted() -> array : + // MatchService : + return MatchService.getAll().filter(state == "NOT STARTED") + + // Afficher un match par id + function get(var long id) -> MatchBean : + return MatchService.get(id) + + // Mettre à jour le score1 d'un match récupéré par id --> TODO + function updateScore1Of(var long id, var integer newScore) -> integer (success or failure) : + return MatchService.update( + + // Mettre à jour le score2 d'un match récupéré par id --> TODO + function updateScore2Of(var long id, var integer newScore) -> integer (success or failure) : + return MatchService.update( + + // Ajouter un match (créer nouveau MatchBean dans MatchRepository) + function add(var MatchBean newMatch) -> integer (success or failure): + return MatchService.add(newMatch) + + // Supprimer un match (supprimer élément de MatchRepository en récupérant l'id) + function deleteById(var long id) -> integer (success or failure) : + return MatchService.deleteById(id) + +RefereeRestController --> DONE: + // Lister tous les arbitres + function getAll() -> array : + return RefereeService.getAll() + + // Afficher un arbitre par id + function get(var long id) -> RefereeBean : + return RefereeService.get(id) + + // Afficher un arbitre par nom + function get(var string name) -> RefereeBean: + return RefereeService.get(name) + + // Ajouter un arbitre (créer nouveau RefereeBean dans RefereeRepository) + function add(var RefereeBean newReferee) -> integer (success or failure) : + return RefereeService.add(newReferee) + + // Supprimer un arbitre (supprimer élément de RefereeRepository en récupérant l'id) + function deleteById(var long id) -> integer (success or failure) : + return RefereeService.deleteById(id) + +PlayerRestController --> DONE: + // Lister tous les joueurs + function getAll() -> array : + return PlayerService.getAll() + + // Afficher un joueur par id + function get(var long id) -> PlayerBean : + return PlayerService.get(id) + + // Afficher un joueur par nom + function get(var string name) -> PlayerBean : + return PlayerService.get(name) + + // Ajouter un joueur (créer nouveau PlayerBean dans PlayerRepository) + function add(var PlayerBean newPlayer) -> integer (success or failure) : + return PlayerService.add(newPlayer) + + // Supprimer un joueur (supprimer élément de PlayerRepository en récupérant l'id) + function deleteById(var long id) -> integer (success or failure) : + return PlayerService.deleteById(id) + +## Pages +Page des matchs (publique) : + à définir (partie front sur Angular ?) + +Page de connexion à l'administration (publique) : + formulaire : + - nom d'utilisateur + - mot de passe + +Page d'administration (privée) : + à définir (partie front sur Angular ?) \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/config/WebSocketConfig.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/config/WebSocketConfig.kt new file mode 100644 index 0000000..341b6f1 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/config/WebSocketConfig.kt @@ -0,0 +1,30 @@ +package fr.teamflash.fencerjudgeback.config + +import org.springframework.context.annotation.Configuration +import org.springframework.messaging.simp.config.MessageBrokerRegistry +import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker +import org.springframework.web.socket.config.annotation.StompEndpointRegistry +import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer + +const val CHANNEL_NAME: String = "/ws/topic" + +@Configuration +@EnableWebSocketMessageBroker +open class WebSocketConfig : WebSocketMessageBrokerConfigurer { + + override fun configureMessageBroker(registry: MessageBrokerRegistry) { + // Enable a simple memory-based message broker to send messages to clients + // Prefix for messages FROM server TO client + registry.enableSimpleBroker(CHANNEL_NAME) + + // Prefix for messages FROM client TO server + registry.setApplicationDestinationPrefixes("/ws") + } + + override fun registerStompEndpoints(registry: StompEndpointRegistry) { + // Register the "/ws" endpoint, enabling SockJS fallback options + registry.addEndpoint("/ws/matches-app") + .setAllowedOriginPatterns("*") // Allow connections from any origin (adjust for production) + .withSockJS() + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/MatchBean.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/MatchBean.kt new file mode 100644 index 0000000..0f95236 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/MatchBean.kt @@ -0,0 +1,33 @@ +package fr.teamflash.fencerjudgeback.entities + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.SequenceGenerator +import jakarta.persistence.Table +import jakarta.validation.constraints.* + +@Entity +@Table(name="matches") +data class MatchBean( + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "match_sequence") + @SequenceGenerator(name = "match_sequence", sequenceName = "match_seq", allocationSize = 1) + val id:Long?=null, + val weapon:String?=null, + val country:String?=null, + val city:String?=null, + @field:NotNull(message = "Player 1 ID must not be null") + val player1ID:Long?=null, + @field:NotNull(message = "Player 2 ID must not be null") + val player2ID:Long?=null, + val refereeID: Long?=null, + @field:Min(value = 0, message = "Score must be at least 0") + var score1:Int=0, + @field:Min(value = 0, message = "Score must be at least 0") + var score2:Int=0, + val date: String?=null, + val state:String?=null +) diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/PlayerBean.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/PlayerBean.kt new file mode 100644 index 0000000..4033340 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/PlayerBean.kt @@ -0,0 +1,20 @@ +package fr.teamflash.fencerjudgeback.entities + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.SequenceGenerator +import jakarta.persistence.Table + +@Entity +@Table(name="players") +data class PlayerBean( + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "player_sequence") + @SequenceGenerator(name = "player_sequence", sequenceName = "player_seq", allocationSize = 1) + val id:Long?=null, + val name:String?="", + val firstName:String?="", + val club:String?="" +) \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/RefereeBean.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/RefereeBean.kt new file mode 100644 index 0000000..4756fee --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/entities/RefereeBean.kt @@ -0,0 +1,20 @@ +package fr.teamflash.fencerjudgeback.entities + +import jakarta.persistence.Entity +import jakarta.persistence.GeneratedValue +import jakarta.persistence.GenerationType +import jakarta.persistence.Id +import jakarta.persistence.SequenceGenerator +import jakarta.persistence.Table + +@Entity +@Table(name="referees") +data class RefereeBean( + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "referee_sequence") + @SequenceGenerator(name = "referee_sequence", sequenceName = "referee_seq", allocationSize = 1) + val id:Long?=null, + val name:String?=null, + val firstName:String?=null, + val qualification:String?=null +) \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/MatchRepository.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/MatchRepository.kt new file mode 100644 index 0000000..728d059 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/MatchRepository.kt @@ -0,0 +1,9 @@ +package fr.teamflash.fencerjudgeback.repositories + +import fr.teamflash.fencerjudgeback.entities.MatchBean +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface MatchRepository: JpaRepository { +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/PlayerRepository.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/PlayerRepository.kt new file mode 100644 index 0000000..ee1eefe --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/PlayerRepository.kt @@ -0,0 +1,9 @@ +package fr.teamflash.fencerjudgeback.repositories + +import fr.teamflash.fencerjudgeback.entities.PlayerBean +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface PlayerRepository: JpaRepository { +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/RefereeRepository.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/RefereeRepository.kt new file mode 100644 index 0000000..102c985 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/repositories/RefereeRepository.kt @@ -0,0 +1,10 @@ +package fr.teamflash.fencerjudgeback.repositories + +import fr.teamflash.fencerjudgeback.entities.RefereeBean +import org.springframework.data.jpa.repository.JpaRepository +import org.springframework.stereotype.Repository + +@Repository +interface RefereeRepository : JpaRepository { + +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/MatchRestController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/MatchRestController.kt new file mode 100644 index 0000000..0957c00 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/MatchRestController.kt @@ -0,0 +1,84 @@ +package fr.teamflash.fencerjudgeback.restControllers + +import fr.teamflash.fencerjudgeback.entities.MatchBean +import fr.teamflash.fencerjudgeback.services.MatchService +import org.springframework.http.ResponseEntity +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/matches") +class MatchRestController(private val matchService: MatchService) { + + // Lister tous les matchs + @GetMapping("/") + fun getAll(): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll()) + } + + // Lister tous les matchs à partir d'une certaine date + @GetMapping("/date/{date}") + fun getAllFromDate(@PathVariable date:String): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll().filter { it.date == date }) + } + + // Lister tous les matchs par pays + @GetMapping("/country/{country}") + fun getAllFromCountry(@PathVariable country:String): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll().filter { it.country == country }) + } + + // Lister tous les matchs par ville + @GetMapping("/city/{city}") + fun getAllFromCity(@PathVariable city:String): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll().filter { it.city == city }) + } + + // Lister tous les matchs par joueurs + @GetMapping("/players/{player1ID}/{player2ID}") + fun getAllWithPlayers(@PathVariable player1ID: Long?, @PathVariable player2ID: Long?): ResponseEntity?> { + return ResponseEntity.ok(matchService.getByPlayers(player1ID, player2ID)) + } + + // Lister les matchs en cours + @GetMapping("/active") + fun getAllActive(): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll().filter { it.state == "ACTIVE" }) + } + + // Lister les matchs terminés + @GetMapping("/over") + fun getAllFinished(): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll().filter { it.state == "FINISHED" }) + } + + // Lister les matchs non commencés + @GetMapping("/not-started") + fun getAllNotStarted(): ResponseEntity?> { + return ResponseEntity.ok(matchService.getAll().filter { it.state == "NOT STARTED" }) + } + + // Afficher un match par id + @GetMapping("/{id}") + fun getById(@PathVariable id: Long): ResponseEntity { + return ResponseEntity.ok(matchService.getById(id)) + } + + // Ajouter un match + @PostMapping("/create-match") + fun createMatch(@RequestBody match: MatchBean): ResponseEntity { +// return ResponseEntity.status(HttpStatus.CREATED).body(matchService.createMatch(match)) + return ResponseEntity.ok(matchService.createMatch(match)) + } + + // Mettre à jour un match + @PutMapping("/update-match/{id}") + fun updateMatch(@PathVariable id: Long, @RequestBody match: MatchBean): ResponseEntity { + return ResponseEntity.ok(matchService.updateMatch(id, match)) + } + + // Supprimer un match + @DeleteMapping("/delete-match/{id}") + fun deleteMatch(@PathVariable id: Long): ResponseEntity { + return ResponseEntity.ok(matchService.deleteMatchById(id)) + } +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/PlayerRestController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/PlayerRestController.kt new file mode 100644 index 0000000..699c557 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/PlayerRestController.kt @@ -0,0 +1,66 @@ +package fr.teamflash.fencerjudgeback.restControllers + +import fr.teamflash.fencerjudgeback.entities.PlayerBean +import fr.teamflash.fencerjudgeback.services.PlayerService +import org.springframework.http.ResponseEntity +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.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/players") +class PlayerRestController(private val playerService: PlayerService) { + + // Lister tous les joueurs + @GetMapping("/") + fun getAll(): ResponseEntity> { + return ResponseEntity.ok(playerService.getAll()) + } + + // Afficher un joueur par id + @GetMapping("/{id}") + fun getById(@PathVariable id: Long): ResponseEntity { + return ResponseEntity.ok(playerService.getById(id)) + } + + // Afficher un ou plusieurs joueur(s) par nom + @GetMapping("/name/{name}") + fun getByName(@PathVariable name: String): ResponseEntity?> { + return ResponseEntity.ok(playerService.getByName(name)) + } + + // Afficher un ou plusieurs joueur(s) par prénom + @GetMapping("/firstName/{firstName}") + fun getByFirstName(@PathVariable firstName: String): ResponseEntity?> { + return ResponseEntity.ok(playerService.getByFirstName(firstName)) + } + + // Afficher un ou plusieurs joueur(s) par club + @GetMapping("/club/{club}") + fun getByClub(@PathVariable club: String): ResponseEntity?> { + return ResponseEntity.ok(playerService.getByClub(club)) + } + + // Ajouter un joueur + @PostMapping("/create-player") + fun createPlayer(@RequestBody player: PlayerBean): ResponseEntity { + return ResponseEntity.ok(playerService.createPlayer(player)) + } + + // Modifier un joueur + @PutMapping("/update-player/{id}") + fun updatePlayer(@PathVariable id: Long, @RequestBody player: PlayerBean): ResponseEntity { + return ResponseEntity.ok(playerService.updatePlayer(id, player)) + } + + // Supprimer un joueur + @DeleteMapping("/delete-player/{id}") + fun deletePlayer(@PathVariable id: Long): ResponseEntity { + return ResponseEntity.ok(playerService.deletePlayerById(id)) + } +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/RefereeRestController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/RefereeRestController.kt new file mode 100644 index 0000000..e26e90f --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/restControllers/RefereeRestController.kt @@ -0,0 +1,67 @@ +package fr.teamflash.fencerjudgeback.restControllers + +import fr.teamflash.fencerjudgeback.entities.RefereeBean +import fr.teamflash.fencerjudgeback.services.RefereeService +import org.springframework.http.ResponseEntity +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.RequestMapping +import org.springframework.web.bind.annotation.RestController + +@RestController +@RequestMapping("/referees") +class RefereeRestController(private val refereeService: RefereeService) { + + // Lister tous les arbitres + @GetMapping("/") + fun getAll() : ResponseEntity> { + return ResponseEntity.ok(refereeService.getAll()) + } + + // Afficher un arbitre par id + @GetMapping("/{id}") + fun getById(@PathVariable id: Long): ResponseEntity { + return ResponseEntity.ok(refereeService.getById(id)) + } + + // Afficher un ou plusieurs arbitre(s) par nom + @GetMapping("/name/{name}") + fun getByName(@PathVariable name:String): ResponseEntity?> { + return ResponseEntity.ok(refereeService.getByName(name)) + } + + // Afficher un ou plusieurs arbitre(s) par prénom + @GetMapping("/firstname/{firstName}") + fun getByFirstName(@PathVariable firstName:String): ResponseEntity?> { + return ResponseEntity.ok(refereeService.getByFirstName(firstName)) + } + + // Afficher un ou plusieurs arbitre(s) par qualification + @GetMapping("/qualification/{qualification}") + fun getByQualification(@PathVariable qualification:String): ResponseEntity?> { + return ResponseEntity.ok(refereeService.getByQualification(qualification)) + } + + // Ajouter un arbitre + @PostMapping("/create-referee") + fun createReferee(@RequestBody referee: RefereeBean): ResponseEntity { + return ResponseEntity.ok(refereeService.createReferee(referee)) + } + + // Modifier un arbitre + @PutMapping("/update-referee/{id}") + fun updateReferee(@PathVariable id: Long, @RequestBody referee: RefereeBean) : ResponseEntity { + return ResponseEntity.ok(refereeService.updateReferee(id, referee)) + } + + // Supprimer un arbitre + @DeleteMapping("/delete-referee/{id}") + fun deleteReferee(@PathVariable id:Long): ResponseEntity { + return ResponseEntity.ok(refereeService.deleteRefereeById(id)) + } + +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/MatchService.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/MatchService.kt new file mode 100644 index 0000000..1e5f24b --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/MatchService.kt @@ -0,0 +1,98 @@ +package fr.teamflash.fencerjudgeback.services + +import fr.teamflash.fencerjudgeback.entities.MatchBean +import fr.teamflash.fencerjudgeback.repositories.MatchRepository +import fr.teamflash.fencerjudgeback.websocket.controllers.MatchWebSocketController +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Lazy +import org.springframework.stereotype.Service + +@Service +class MatchService( + @Autowired private val matchRepository: MatchRepository, + @Lazy private val matchWebSocketController: MatchWebSocketController? = null +) { + // Obtenir tous les matchs (public) + fun getAll() : List { + println("MatchService.getMatchs") + return matchRepository.findAll() + } + + // Obtenir un match par id (public) + fun getById(id: Long?): MatchBean? { + println("MatchService.getMatchById : $id") + + if (id == null) { + println("MatchService.getMatchById : Match not found") + return null + } + + return matchRepository.findById(id).get() + } + + // Obtenir un ou plusieurs match(s) par joueurs (id) (public) + fun getByPlayers(player1ID: Long?, player2ID: Long?): List { + println("MatchService.getMatchByPlayers : $player1ID - $player2ID") + return matchRepository.findAll().filter { it.player1ID == player1ID && it.player2ID == player2ID } + } + + // Ajouter un match (admin) + fun createMatch(newMatch: MatchBean): MatchBean { + println("MatchService.createMatch : $newMatch") + val savedMatch = matchRepository.save(newMatch) + // Broadcast the new match via WebSocket + matchWebSocketController?.broadcastMatchUpdate(savedMatch, fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage.UpdateType.MATCH_START) + + return savedMatch + } + + // Modifier un match (admin) + fun updateMatch(id: Long?, newMatch: MatchBean): Int { + println("MatchService.updateMatch : $newMatch") + + if (getById(id) == null) { + return -1 + } + + val updatedMatch = newMatch.copy(id = id) + val savedMatch = matchRepository.save(updatedMatch) + // Broadcast the updated match via WebSocket + matchWebSocketController?.broadcastMatchUpdate(savedMatch, fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage.UpdateType.SCORE_UPDATE) + return 1 + } + + // Supprimer un match (admin) + fun deleteMatchById(id: Long?) : Int? { + println("MatchService.deleteMatchById : $id") + + val match = getById(id) + if (match == null) { + println("MatchService.deleteMatchById : Match not found") + return -1 + } + + // Broadcast the match deletion via WebSocket before deleting + matchWebSocketController?.broadcastMatchUpdate(match, fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage.UpdateType.MATCH_CANCEL) + + matchRepository.deleteById(id!!) + return 1 + } + + fun getMatchesByCity(city: String): List? { + println("MatchService.getMatchesByCity : $city") + + return matchRepository.findAll() + .filter { it.city == city } + } + + fun getMatchesByCountry(country: String): List? { + println("MatchService.getMatchesByCountry : $country") + + return matchRepository.findAll() + .filter { it.country == country } + } + + fun addMatch(match:MatchBean) { + matchRepository.save(match) + } +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/PlayerService.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/PlayerService.kt new file mode 100644 index 0000000..0443f0e --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/PlayerService.kt @@ -0,0 +1,96 @@ +package fr.teamflash.fencerjudgeback.services + +import fr.teamflash.fencerjudgeback.entities.PlayerBean +import fr.teamflash.fencerjudgeback.repositories.PlayerRepository +import fr.teamflash.fencerjudgeback.websocket.controllers.PlayerWebSocketController +import fr.teamflash.fencerjudgeback.websocket.models.PlayerUpdateMessage +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Lazy +import org.springframework.stereotype.Service + +@Service +class PlayerService( + @Autowired private val playerRepository: PlayerRepository, + @Lazy private val playerWebSocketController: PlayerWebSocketController? = null +) { + // Obtenir tous les joueurs (public) + fun getAll(): List { + println("PlayerService.getAll") + return playerRepository.findAll() + } + + // Obtenir un joueur par id (public) + fun getById(id:Long?) : PlayerBean? { + println("PlayerService.getById : $id") + + if (id == null) { + return null + } + + return playerRepository.findById(id).get() + } + + // Obtenir un ou plusieurs joueur(s) par nom (public) + fun getByName(name:String): List? { + println("PlayerService.getByName : $name") + return playerRepository.findAll().filter{ it.name == name } + } + + // Obtenir un ou plusieurs joueur(s) par prénom + fun getByFirstName(firstName:String): List? { + println("PlayerService.getByFirstName : $firstName") + return playerRepository.findAll().filter{ it.firstName == firstName } + } + + // Obtenir un ou plusieurs joueur(s) par club + fun getByClub(club:String): List? { + println("PlayerService.getByClub : $club") + return playerRepository.findAll().filter{ it.club == club } + } + + // Ajouter un joueur (admin) + fun createPlayer(player: PlayerBean) : PlayerBean { + println("PlayerService.createPlayer : $player") + val savedPlayer = playerRepository.save(player) + // Broadcast the new player via WebSocket + playerWebSocketController?.broadcastPlayerUpdate(savedPlayer, PlayerUpdateMessage.UpdateType.PLAYER_CREATE) + return savedPlayer + } + + // Modifier un joueur (admin) + fun updatePlayer(id:Long, newPlayer: PlayerBean) : Int { + // Vérifier si le joueur existe à l'id renseigné + if (getById(id) == null) { + println("PlayerService.updatePlayer : Player not found") + return -1 + } + + val updatedPlayer = newPlayer.copy(id = id) + val savedPlayer = playerRepository.save(updatedPlayer) + // Broadcast the updated player via WebSocket + playerWebSocketController?.broadcastPlayerUpdate(savedPlayer, PlayerUpdateMessage.UpdateType.PLAYER_UPDATE) + + return 1 + } + + // Supprimer un joueur (admin) + fun deletePlayerById(id:Long?): Int { + println("PlayerService.deletePlayer : $id") + + val player = getById(id) + if (player == null) { + println("PlayerService.deletePlayer : Player not found") + return -1 + } + + // Broadcast the player deletion via WebSocket before deleting + playerWebSocketController?.broadcastPlayerUpdate(player, PlayerUpdateMessage.UpdateType.PLAYER_DELETE) + + playerRepository.deleteById(id!!) + return 1 + } + + fun addPlayer(player:PlayerBean) { + playerRepository.save(player) + } +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/RefereeService.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/RefereeService.kt new file mode 100644 index 0000000..218a1cb --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/services/RefereeService.kt @@ -0,0 +1,90 @@ +package fr.teamflash.fencerjudgeback.services + +import fr.teamflash.fencerjudgeback.entities.RefereeBean +import fr.teamflash.fencerjudgeback.repositories.RefereeRepository +import fr.teamflash.fencerjudgeback.websocket.controllers.RefereeWebSocketController +import fr.teamflash.fencerjudgeback.websocket.models.RefereeUpdateMessage +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Lazy +import org.springframework.stereotype.Service + +@Service +class RefereeService( + @Autowired private val refereeRepository: RefereeRepository, + @Lazy private val refereeWebSocketController: RefereeWebSocketController? = null +) { + // Obtenir tous les arbitres (public) + fun getAll(): List { + println("RefereeService.getReferees") + return refereeRepository.findAll() + } + + // Obtenir un arbitre par id (public) + fun getById(id:Long?) : RefereeBean? { + println("RefereeService.getRefereeById : $id") + + if (id == null) { + return null + } + + return refereeRepository.findById(id).get() + } + + // Obtenir un ou plusieurs arbitre(s) par nom (public) + fun getByName(name:String): List? { + println("RefereeService.getRefereeByName : $name") + return refereeRepository.findAll().filter{ it.name == name } + } + + // Obtenir un ou plusieurs arbitre(s) par prénom + fun getByFirstName(firstName:String): List? { + println("RefereeService.getRefereeByFirstName : $firstName") + return refereeRepository.findAll().filter{ it.firstName == firstName } + } + + // Obtenir un ou plusieurs arbitre(s) par qualification + fun getByQualification(qualification:String): List? { + println("RefereeService.getRefereeByQualification : $qualification") + return refereeRepository.findAll().filter{ it.qualification == qualification } + } + + // Ajouter un arbitre (admin) + fun createReferee(referee: RefereeBean) : RefereeBean { + println("RefereeService.createReferee : $referee") + val savedReferee = refereeRepository.save(referee) + // Broadcast the new referee via WebSocket + refereeWebSocketController?.broadcastRefereeUpdate(savedReferee, RefereeUpdateMessage.UpdateType.REFEREE_CREATE) + return savedReferee + } + + // Modifier un arbitre (admin) + fun updateReferee(id:Long, newReferee: RefereeBean) : Int? { + // Vérifier si l'arbitre existe à l'id renseigné + if (getById(id) == null) { + return -1 + } + + val updatedReferee = newReferee.copy(id = id) + val savedReferee = refereeRepository.save(updatedReferee) + // Broadcast the updated referee via WebSocket + refereeWebSocketController?.broadcastRefereeUpdate(savedReferee, RefereeUpdateMessage.UpdateType.REFEREE_UPDATE) + return 1 + } + + // Supprimer un arbitre (admin) + fun deleteRefereeById(id:Long): Int { + println("RefereeService.deleteReferee : $id") + + val referee = getById(id) + if (referee == null) { + println("RefereeService.deleteReferee : Referee not found") + return -1 + } + + // Broadcast the referee deletion via WebSocket before deleting + refereeWebSocketController?.broadcastRefereeUpdate(referee, RefereeUpdateMessage.UpdateType.REFEREE_DELETE) + + refereeRepository.deleteById(id) + return 1 + } +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/README.md b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/README.md new file mode 100644 index 0000000..ebd2037 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/README.md @@ -0,0 +1,120 @@ +# WebSocket Implementation for FencerJudge + +This document explains how to use the WebSocket functionality in the FencerJudge application for real-time match updates. + +## Overview + +The WebSocket implementation allows for real-time updates of match information, including: +- Match creation +- Score updates +- Match completion +- Match cancellation + +## WebSocket Endpoints + +### Connection Endpoint +``` +ws://localhost:8080/ws +``` + +The WebSocket endpoint supports SockJS for fallback in browsers that don't support WebSockets natively. + +## Message Topics + +### Subscribe to Match Updates +To receive real-time match updates, subscribe to: +``` +/topic/match.updates +``` + +### Send Match Updates +To send match updates from the client to the server: +``` +/app/match.update +``` + +## Message Format + +Match update messages use the following JSON format: + +```json +{ + "matchId": 1, + "player1Id": 101, + "player2Id": 102, + "refereeId": 201, + "score1": 5, + "score2": 3, + "date": "2023-06-01", + "type": "SCORE_UPDATE" +} +``` + +The `type` field can have the following values: +- `SCORE_UPDATE`: When a match score is updated +- `MATCH_START`: When a new match is created +- `MATCH_END`: When a match is completed +- `MATCH_CANCEL`: When a match is cancelled + +## Integration with REST API + +The WebSocket functionality is integrated with the REST API. Any changes made through the REST endpoints will automatically trigger WebSocket messages to all connected clients. + +REST endpoints: +- `GET /api/matches`: Get all matches +- `GET /api/matches/{id}`: Get a match by ID +- `POST /api/matches`: Create a new match (triggers MATCH_START WebSocket message) +- `PUT /api/matches/{id}`: Update a match (triggers SCORE_UPDATE WebSocket message) +- `PATCH /api/matches/{id}/score`: Update just the score (triggers SCORE_UPDATE WebSocket message) +- `DELETE /api/matches/{id}`: Delete a match (triggers MATCH_CANCEL WebSocket message) + +## Client-Side Example (JavaScript) + +```javascript +// Using SockJS and STOMP client +import SockJS from 'sockjs-client'; +import { Client } from '@stomp/stompjs'; + +// Create a new STOMP client +const socket = new SockJS('http://localhost:8080/ws'); +const stompClient = new Client({ + webSocketFactory: () => socket, + debug: (str) => console.log(str) +}); + +// Connect to the WebSocket server +stompClient.activate(); + +// Subscribe to match updates +stompClient.onConnect = (frame) => { + console.log('Connected to WebSocket'); + + // Subscribe to match updates + stompClient.subscribe('/topic/match.updates', (message) => { + const matchUpdate = JSON.parse(message.body); + console.log('Received match update:', matchUpdate); + + // Handle the update in your application + // e.g., update UI, play sound, etc. + }); +}; + +// Send a match update (e.g., when a referee updates a score) +function sendMatchUpdate(matchId, score1, score2) { + if (stompClient.connected) { + stompClient.publish({ + destination: '/app/match.update', + body: JSON.stringify({ + matchId: matchId, + score1: score1, + score2: score2, + type: 'SCORE_UPDATE' + }) + }); + } +} +``` + +## Security Considerations + +The WebSocket endpoint is currently configured to allow connections from any origin (`*`). For production environments, this should be restricted to specific allowed origins. \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/WebsocketController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/WebsocketController.kt new file mode 100644 index 0000000..af33708 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/WebsocketController.kt @@ -0,0 +1,41 @@ +package fr.teamflash.fencerjudgeback.websocket + +import org.springframework.messaging.handler.annotation.MessageMapping +import org.springframework.messaging.simp.SimpMessagingTemplate +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping + +data class Data(val id: String, val value: Any) + +@Controller +@RequestMapping("/ws") +class WebSocketController(private val messagingTemplate: SimpMessagingTemplate) { + + // Insérer une donnée dans le channel + @MessageMapping("/insert") + fun insertData(data: Data) { + // Logique d'insertion + messagingTemplate.convertAndSend("/topic/data", data) + } + + // Modifier une donnée existente dans le channel + @MessageMapping("/modify") + fun modifyData(data: Data) { + // Logique de modification + messagingTemplate.convertAndSend("/topic/data", data) + } + + // Supprimer une donnée existante dans le channel + @MessageMapping("/delete") + fun deleteData(id: String) { + // Logique de suppression + messagingTemplate.convertAndSend("/topic/data/delete", id) + } + + // Vider le channel + @MessageMapping("/clear") + fun clearChannel() { + // Logique pour vider le channel + messagingTemplate.convertAndSend("/topic/data/clear", null) + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/MatchWebSocketController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/MatchWebSocketController.kt new file mode 100644 index 0000000..e75518d --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/MatchWebSocketController.kt @@ -0,0 +1,102 @@ +package fr.teamflash.fencerjudgeback.websocket.controllers + +import fr.teamflash.fencerjudgeback.config.CHANNEL_NAME +import fr.teamflash.fencerjudgeback.entities.MatchBean +import fr.teamflash.fencerjudgeback.services.MatchService +import fr.teamflash.fencerjudgeback.websocket.models.MatchUpdateMessage +import org.springframework.context.event.EventListener +import org.springframework.messaging.handler.annotation.MessageMapping +import org.springframework.messaging.simp.SimpMessagingTemplate +import org.springframework.messaging.simp.stomp.StompHeaderAccessor +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.socket.messaging.SessionSubscribeEvent + +@Controller +@RequestMapping("/ws/matches") +class MatchWebSocketController( + private val matchService: MatchService, + private val messagingTemplate: SimpMessagingTemplate +) { + + private val messageHistory = ArrayList() + + @MessageMapping("/all") + fun receiveMessage(message: MatchBean) { + println("/ws/matches $message") + messageHistory.add(message) + + // Envoyer la liste des messages sur le channel + //Si la variable est dans le même package il faut enlever WebSocketConfig. + messagingTemplate.convertAndSend(CHANNEL_NAME, messageHistory) + } + + /** + * Handle match update requests from clients + * Client sends to: /app/match.update + * Server broadcasts to: /topic/match.updates + */ + @MessageMapping("/update") + fun handleMatchUpdate(matchUpdateMessage: MatchUpdateMessage): MatchUpdateMessage { + // Create a MatchBean from the update message + val matchBean = MatchBean( + id = matchUpdateMessage.matchId, + player1ID = matchUpdateMessage.player1Id, + player2ID = matchUpdateMessage.player2Id, + refereeID = matchUpdateMessage.refereeId, + score1 = matchUpdateMessage.score1, + score2 = matchUpdateMessage.score2, + date = matchUpdateMessage.date + ) + + // Update the match in the database + matchService.updateMatch(matchBean.id, matchBean) + + // Return the update message to be broadcast to all subscribers + return matchUpdateMessage + } + + /** + * Broadcast a match update to all connected clients + * This method can be called from other services + */ + fun broadcastMatchUpdate(matchBean: MatchBean, type: MatchUpdateMessage.UpdateType = MatchUpdateMessage.UpdateType.SCORE_UPDATE) { + val updateMessage = MatchUpdateMessage.fromMatchBean(matchBean, type) + messagingTemplate.convertAndSend("/topic/match.updates", updateMessage) + } + + //A mettre dans le controller + @EventListener + fun handleWebSocketSubscribeListener(event: SessionSubscribeEvent) { + val headerAccessor = StompHeaderAccessor.wrap(event.message) + if (CHANNEL_NAME == headerAccessor.destination) { + messagingTemplate.convertAndSend(CHANNEL_NAME, messageHistory) + println("Lancement...") + } + } + + fun addPoint(match:MatchBean, playerId:Long) { + when (playerId) { + match.player1ID -> match.score1 += 1 + match.player2ID -> match.score2 += 1 + } + matchService.updateMatch(match.id, match) + broadcastMatchUpdate(match) + } + + fun minusPoint(match:MatchBean, playerId:Long) { + when (playerId) { + match.player1ID -> if (match.score1 > 0) match.score1 -= 1 + match.player2ID -> if (match.score2 > 0) match.score2 -= 1 + } + matchService.updateMatch(match.id, match) + broadcastMatchUpdate(match) + } + + @MessageMapping("/add") + fun addMatchtoMainList(match:MatchBean) { + matchService.addMatch(match) + messageHistory.add(match) + broadcastMatchUpdate(match, MatchUpdateMessage.UpdateType.NEW_MATCH) + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/PlayerWebSocketController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/PlayerWebSocketController.kt new file mode 100644 index 0000000..4e31270 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/PlayerWebSocketController.kt @@ -0,0 +1,71 @@ +package fr.teamflash.fencerjudgeback.websocket.controllers + +import fr.teamflash.fencerjudgeback.entities.PlayerBean +import fr.teamflash.fencerjudgeback.services.PlayerService +import fr.teamflash.fencerjudgeback.websocket.models.PlayerUpdateMessage +import org.springframework.context.event.EventListener +import org.springframework.messaging.handler.annotation.MessageMapping +import org.springframework.messaging.handler.annotation.SendTo +import org.springframework.messaging.simp.SimpMessagingTemplate +import org.springframework.messaging.simp.stomp.StompHeaderAccessor +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.socket.messaging.SessionSubscribeEvent + +@Controller +@RequestMapping("/ws") +class PlayerWebSocketController( + private val playerService: PlayerService, + private val messagingTemplate: SimpMessagingTemplate +) { + + private val messageHistory = ArrayList() + + @MessageMapping("/player.update") + @SendTo("/topic/player.updates") + fun handlePlayerUpdate(playerUpdateMessage: PlayerUpdateMessage): PlayerUpdateMessage { + // Create a PlayerBean from the update message + val playerBean = PlayerBean( + id = playerUpdateMessage.playerId, + name = playerUpdateMessage.name, + firstName = playerUpdateMessage.firstName, + club = playerUpdateMessage.club + ) + + // Update the player in the database + playerService.updatePlayer(playerBean.id ?: 0, playerBean) + + // Return the update message to be broadcast to all subscribers + return playerUpdateMessage + } + + /** + * Broadcast a player update to all connected clients + * This method can be called from other services + */ + fun broadcastPlayerUpdate(playerBean: PlayerBean, type: PlayerUpdateMessage.UpdateType = PlayerUpdateMessage.UpdateType.PLAYER_UPDATE) { + val updateMessage = PlayerUpdateMessage.fromPlayerBean(playerBean, type) + messagingTemplate.convertAndSend("/topic/player.updates", updateMessage) + } + + @EventListener + fun handleWebSocketSubscribeListener(event: SessionSubscribeEvent) { + val headerAccessor = StompHeaderAccessor.wrap(event.message) + if ("/topic/player.updates" == headerAccessor.destination) { + // When a client subscribes, send them the current players + val players = playerService.getAll() + players.forEach { player -> + messagingTemplate.convertAndSend( + "/topic/player.updates", + PlayerUpdateMessage.fromPlayerBean(player, PlayerUpdateMessage.UpdateType.PLAYER_UPDATE) + ) + } + } + } + + @MessageMapping + fun addPlayerToMainList(playerBean: PlayerBean) { + playerService.addPlayer(playerBean) + broadcastPlayerUpdate(playerBean, PlayerUpdateMessage.UpdateType.PLAYER_CREATE) + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/RefereeWebSocketController.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/RefereeWebSocketController.kt new file mode 100644 index 0000000..2b21601 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/controllers/RefereeWebSocketController.kt @@ -0,0 +1,66 @@ +package fr.teamflash.fencerjudgeback.websocket.controllers + +import fr.teamflash.fencerjudgeback.config.CHANNEL_NAME +import fr.teamflash.fencerjudgeback.entities.RefereeBean +import fr.teamflash.fencerjudgeback.services.RefereeService +import fr.teamflash.fencerjudgeback.websocket.models.RefereeUpdateMessage +import org.springframework.context.event.EventListener +import org.springframework.messaging.handler.annotation.MessageMapping +import org.springframework.messaging.handler.annotation.SendTo +import org.springframework.messaging.simp.SimpMessagingTemplate +import org.springframework.messaging.simp.stomp.StompHeaderAccessor +import org.springframework.stereotype.Controller +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.socket.messaging.SessionSubscribeEvent + +@Controller +@RequestMapping("/ws") +class RefereeWebSocketController( + private val refereeService: RefereeService, + private val messagingTemplate: SimpMessagingTemplate +) { + + private val messageHistory = ArrayList() + + @MessageMapping("/referee.update") + @SendTo("/topic/referee.updates") + fun handleRefereeUpdate(refereeUpdateMessage: RefereeUpdateMessage): RefereeUpdateMessage { + // Create a RefereeBean from the update message + val refereeBean = RefereeBean( + id = refereeUpdateMessage.refereeId, + name = refereeUpdateMessage.name, + firstName = refereeUpdateMessage.firstName, + qualification = refereeUpdateMessage.qualification + ) + + // Update the referee in the database + refereeService.updateReferee(refereeBean.id ?: 0, refereeBean) + + // Return the update message to be broadcast to all subscribers + return refereeUpdateMessage + } + + /** + * Broadcast a referee update to all connected clients + * This method can be called from other services + */ + fun broadcastRefereeUpdate(refereeBean: RefereeBean, type: RefereeUpdateMessage.UpdateType = RefereeUpdateMessage.UpdateType.REFEREE_UPDATE) { + val updateMessage = RefereeUpdateMessage.fromRefereeBean(refereeBean, type) + messagingTemplate.convertAndSend("/topic/referee.updates", updateMessage) + } + + @EventListener + fun handleWebSocketSubscribeListener(event: SessionSubscribeEvent) { + val headerAccessor = StompHeaderAccessor.wrap(event.message) + if ("/topic/referee.updates" == headerAccessor.destination) { + // When a client subscribes, send them the current referees + val referees = refereeService.getAll() + referees.forEach { referee -> + messagingTemplate.convertAndSend( + "/topic/referee.updates", + RefereeUpdateMessage.fromRefereeBean(referee, RefereeUpdateMessage.UpdateType.REFEREE_UPDATE) + ) + } + } + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/MatchUpdateMessage.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/MatchUpdateMessage.kt new file mode 100644 index 0000000..028845f --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/MatchUpdateMessage.kt @@ -0,0 +1,43 @@ +package fr.teamflash.fencerjudgeback.websocket.models + +import fr.teamflash.fencerjudgeback.entities.MatchBean + +/** + * Message model for match updates sent through WebSocket + */ +data class MatchUpdateMessage( + val matchId: Long, + val player1Id: Long?, + val player2Id: Long?, + val refereeId: Long?, + val score1: Int, + val score2: Int, + val date: String?, + val type: UpdateType = UpdateType.SCORE_UPDATE +) { + enum class UpdateType { + SCORE_UPDATE, + MATCH_START, + MATCH_END, + MATCH_CANCEL, + NEW_MATCH, + } + + companion object { + /** + * Create a MatchUpdateMessage from a MatchBean + */ + fun fromMatchBean(matchBean: MatchBean, type: UpdateType = UpdateType.SCORE_UPDATE): MatchUpdateMessage { + return MatchUpdateMessage( + matchId = matchBean.id ?: 0, + player1Id = matchBean.player1ID, + player2Id = matchBean.player2ID, + refereeId = matchBean.refereeID, + score1 = matchBean.score1, + score2 = matchBean.score2, + date = matchBean.date, + type = type + ) + } + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/PlayerUpdateMessage.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/PlayerUpdateMessage.kt new file mode 100644 index 0000000..c45b1e5 --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/PlayerUpdateMessage.kt @@ -0,0 +1,35 @@ +package fr.teamflash.fencerjudgeback.websocket.models + +import fr.teamflash.fencerjudgeback.entities.PlayerBean + +/** + * Message model for player updates sent through WebSocket + */ +data class PlayerUpdateMessage( + val playerId: Long, + val name: String?, + val firstName: String?, + val club: String?, + val type: UpdateType = UpdateType.PLAYER_UPDATE +) { + enum class UpdateType { + PLAYER_UPDATE, + PLAYER_CREATE, + PLAYER_DELETE + } + + companion object { + /** + * Create a PlayerUpdateMessage from a PlayerBean + */ + fun fromPlayerBean(playerBean: PlayerBean, type: UpdateType = UpdateType.PLAYER_UPDATE): PlayerUpdateMessage { + return PlayerUpdateMessage( + playerId = playerBean.id ?: 0, + name = playerBean.name, + firstName = playerBean.firstName, + club = playerBean.club, + type = type + ) + } + } +} diff --git a/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/RefereeUpdateMessage.kt b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/RefereeUpdateMessage.kt new file mode 100644 index 0000000..cb9c1be --- /dev/null +++ b/FencerJudgeBack/src/main/kotlin/fr/teamflash/fencerjudgeback/websocket/models/RefereeUpdateMessage.kt @@ -0,0 +1,35 @@ +package fr.teamflash.fencerjudgeback.websocket.models + +import fr.teamflash.fencerjudgeback.entities.RefereeBean + +/** + * Message model for referee updates sent through WebSocket + */ +data class RefereeUpdateMessage( + val refereeId: Long, + val name: String?, + val firstName: String?, + val qualification: String?, + val type: UpdateType = UpdateType.REFEREE_UPDATE +) { + enum class UpdateType { + REFEREE_UPDATE, + REFEREE_CREATE, + REFEREE_DELETE + } + + companion object { + /** + * Create a RefereeUpdateMessage from a RefereeBean + */ + fun fromRefereeBean(refereeBean: RefereeBean, type: UpdateType = UpdateType.REFEREE_UPDATE): RefereeUpdateMessage { + return RefereeUpdateMessage( + refereeId = refereeBean.id ?: 0, + name = refereeBean.name, + firstName = refereeBean.firstName, + qualification = refereeBean.qualification, + type = type + ) + } + } +} \ No newline at end of file diff --git a/FencerJudgeBack/src/main/resources/application.properties b/FencerJudgeBack/src/main/resources/application.properties index 6ada312..0084aa4 100644 --- a/FencerJudgeBack/src/main/resources/application.properties +++ b/FencerJudgeBack/src/main/resources/application.properties @@ -1 +1,16 @@ spring.application.name=FencerJudgeBack + +# H2 Database Configuration +spring.datasource.url=jdbc:h2:file:./db;AUTO_SERVER=true +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect + +# Enable H2 Console +spring.h2.console.enabled=true +spring.h2.console.path=/h2-console + +# Hibernate Configuration +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true \ No newline at end of file diff --git a/FencerJudgeBack/src/main/resources/static/testWebSocketHTML.html b/FencerJudgeBack/src/main/resources/static/testWebSocketHTML.html new file mode 100644 index 0000000..1009354 --- /dev/null +++ b/FencerJudgeBack/src/main/resources/static/testWebSocketHTML.html @@ -0,0 +1,87 @@ + + + + + Test WebSocket Match + + + + +

Test WebSocket Match

+ +
Connexion...
+ +
+ + + + +
+ +
+ + + +
+ + + + + + + + + + diff --git a/FencerJudgeBack/test.http b/FencerJudgeBack/test.http new file mode 100644 index 0000000..6a6444e --- /dev/null +++ b/FencerJudgeBack/test.http @@ -0,0 +1,138 @@ +### Get all matches +GET http://localhost:8080/matches/ + +### Get matches by players --> TODO +GET http://localhost:8080/matches/players/1/2 + +### Get matches by date +GET http://localhost:8080/matches/date/2025-06-02 + +### Get matches by country +GET http://localhost:8080/matches/country/France + +### Get matches by city +GET http://localhost:8080/matches/city/Paris + +### Get active matches +GET http://localhost:8080/matches/active + +### Get finished matches +GET http://localhost:8080/matches/over + +### Get not started matches +GET http://localhost:8080/matches/not-started + +### Get match by ID +GET http://localhost:8080/matches/2 + +### Create new match +POST http://localhost:8080/matches/create-match +Content-Type: application/json + +{ + "weapon": "Épée", + "country": "France", + "city": "Paris", + "player1ID": 1, + "player2ID": 2, + "refereeID": 1, + "score1": 0, + "score2": 0, + "date": "2025-06-02", + "state": "NOT STARTED" +} + +### Update match +PUT http://localhost:8080/matches/update-match/4 +Content-Type: application/json + +{ + + "weapon": "Épée", + "country": "France", + "city": "Paris", + "player1ID": 1, + "player2ID": 2, + "refereeID": 1, + "score1": 5, + "score2": 3, + "date": "2025-06-02", + "state": "OVER" +} + +### Delete match +DELETE http://localhost:8080/matches/delete-match/3 + +### Get all referees +GET http://localhost:8080/referees/ + +### Get referee by ID +GET http://localhost:8080/referees/1 + +### Get referees by name +GET http://localhost:8080/referees/name/Smith + +### Get referees by firstname +GET http://localhost:8080/referees/firstname/John + +### Get referees by qualification +GET http://localhost:8080/referees/qualification/NATIONAL + +### Create new referee +POST http://localhost:8080/referees/create-referee +Content-Type: application/json + +{ + "name": "Smith", + "firstName": "John", + "qualification": "NATIONAL" +} + +### Update referee +PUT http://localhost:8080/referees/update-referee/6 +Content-Type: application/json + +{ + "name": "Smith", + "firstName": "John", + "qualification": "INTERNATIONAL" +} + +### Delete referee +DELETE http://localhost:8080/referees/delete-referee/5 + +### Get all players +GET http://localhost:8080/players/ + +### Get player by ID +GET http://localhost:8080/players/10 + +### Get players by name +GET http://localhost:8080/players/name/Doe + +### Get players by firstname +GET http://localhost:8080/players/firstname/Jane + +### Create new player +POST http://localhost:8080/players/create-player +Content-Type: application/json + +{ + "name": "Doe", + "firstName": "Jane", + "club": "Paris Escrime Club" +} + +### Update player +PUT http://localhost:8080/players/update-player/10 +Content-Type: application/json + +{ + "name": "Doe", + "firstName": "Jane", + "club": "Un autre club" +} + +### Delete player +DELETE http://localhost:8080/players/delete-player/10 +