From 922916e233256ad7b1021607b685c09e62f1778f Mon Sep 17 00:00:00 2001 From: Janis Date: Wed, 3 Dec 2025 20:33:20 +0100 Subject: [PATCH 1/8] feat: BAC-27 Implement the API Endpoint on the backend --- .../app/controllers/StatusController.scala | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 knockoutwhistweb/app/controllers/StatusController.scala diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala new file mode 100644 index 0000000..246579d --- /dev/null +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -0,0 +1,120 @@ +package controllers + +import auth.{AuthAction, AuthenticatedRequest} +import de.knockoutwhist.control.GameState.{FinishedMatch, InGame, Lobby, MainMenu, SelectTrump, TieBreak} +import logic.PodManager +import logic.game.GameLobby +import logic.user.SessionManager +import model.users.User +import play.api.libs.json.{JsValue, Json} +import play.api.mvc.{Action, *} + +import javax.inject.Inject + +class StatusController @Inject()( + val controllerComponents: ControllerComponents, + val sessionManager: SessionManager, + val authAction: AuthAction + ) extends BaseController { + + def requestStatus(): Action[AnyContent] = { + Action { implicit request => + val userOpt = getUserFromSession(request) + if (userOpt.isEmpty) { + Ok( + Json.obj( + "status" -> "unauthenticated" + ) + ) + } else { + val user = userOpt.get + val gameOpt = PodManager.identifyGameOfUser(user) + if (gameOpt.isEmpty) { + Ok( + Json.obj( + "status" -> "authenticated", + "username" -> user.name, + "inGame" -> "false" + ) + ) + } else { + val game = gameOpt.get + Ok( + Json.obj( + "status" -> "authenticated", + "username" -> user.name, + "inGame" -> "true", + "gameId" -> game.id + ) + ) + } + } + } + } + + def game(gameId: String): Action[AnyContent] = { + Action { implicit request => + val userOpt = getUserFromSession(request) + if (userOpt.isEmpty) { + Unauthorized("User not authenticated") + } else { + val user = userOpt.get + val gameOpt = PodManager.getGame(gameId) + if (gameOpt.isEmpty) { + NotFound("Game not found") + } else { + val game = gameOpt.get + if (!game.getPlayers.contains(user.id)) { + Forbidden("User not part of this game") + } else { + Ok( + Json.obj( + "gameId" -> game.id, + "state" -> game.logic.getCurrentState.toString, + "data" -> mapGameState(game, user) + ) + ) + } + } + } + }} + + private def getUserFromSession(request: RequestHeader): Option[User] = { + val session = request.cookies.get("sessionId") + if (session.isDefined) + return sessionManager.getUserBySession(session.get.value) + None + } + + private def mapGameState(gameLobby: GameLobby, user: User): JsValue = { + val userSession = gameLobby.getUserSession(user.id) + gameLobby.logic.getCurrentState match { + case Lobby => + Json.obj( + "host" -> userSession.host, + "players" -> gameLobby.getUsers.map(p => Json.obj("id" -> p.id, "name" -> p.name, "isSelf" -> (p.id == user.id))) + ) + case SelectTrump => + Json.obj( + + ) + case MainMenu => + Json.obj( + + ) + case InGame => + Json.obj( + + ) + case TieBreak => + Json.obj( + + ) + case FinishedMatch => + Json.obj( + + ) + } + } + +} -- 2.52.0 From 47ec146aaded358e1c6586eca29487404dd390be Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 4 Dec 2025 09:21:17 +0100 Subject: [PATCH 2/8] feat: Implemented select trump for BAC-27 --- .../app/controllers/StatusController.scala | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index 246579d..0672fbb 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -2,12 +2,14 @@ package controllers import auth.{AuthAction, AuthenticatedRequest} import de.knockoutwhist.control.GameState.{FinishedMatch, InGame, Lobby, MainMenu, SelectTrump, TieBreak} +import de.knockoutwhist.player.AbstractPlayer import logic.PodManager import logic.game.GameLobby import logic.user.SessionManager import model.users.User import play.api.libs.json.{JsValue, Json} import play.api.mvc.{Action, *} +import util.WebUIUtils import javax.inject.Inject @@ -95,16 +97,44 @@ class StatusController @Inject()( "players" -> gameLobby.getUsers.map(p => Json.obj("id" -> p.id, "name" -> p.name, "isSelf" -> (p.id == user.id))) ) case SelectTrump => - Json.obj( - - ) + val findSelector: Option[AbstractPlayer] = gameLobby.logic.getCurrentMatch match { + case Some(matchImpl) => + if (matchImpl.roundlist.isEmpty) None + else { + matchImpl.roundlist.last.winner match { + case Some(winner) => Some(winner) + case None => None + } + } + case None => None + } + + findSelector match { + case Some(selector) => + val isSelf = selector.id == user.id + val playerHand = { + val userPlayer = gameLobby.getPlayerByUser(user) + val handOpt = userPlayer.currentHand() + handOpt match { + case Some(hand) => + WebUIUtils.handToJson(hand) + case None => Json.arr() + } + } + Json.obj( + "selector" -> selector.name, + "isSelf" -> isSelf, + "hand" -> playerHand + ) + case None => Json.obj( + "error" -> "No winner found. Please try again later." + ) + } case MainMenu => - Json.obj( - - ) + Json.obj() case InGame => Json.obj( - + ) case TieBreak => Json.obj( @@ -112,8 +142,7 @@ class StatusController @Inject()( ) case FinishedMatch => Json.obj( - - ) + ) } } -- 2.52.0 From 17a200c929a20767f8af7a486c54940e03c0a775 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 4 Dec 2025 09:55:32 +0100 Subject: [PATCH 3/8] feat: Implemented select trump for BAC-27 --- knockoutwhistweb/app/controllers/StatusController.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index 0672fbb..a52daab 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -108,7 +108,7 @@ class StatusController @Inject()( } case None => None } - + findSelector match { case Some(selector) => val isSelf = selector.id == user.id @@ -134,7 +134,7 @@ class StatusController @Inject()( Json.obj() case InGame => Json.obj( - + ) case TieBreak => Json.obj( @@ -142,6 +142,7 @@ class StatusController @Inject()( ) case FinishedMatch => Json.obj( + ) } } -- 2.52.0 From e3fc18f1993150e889b0e0a0a096aa698552a0c5 Mon Sep 17 00:00:00 2001 From: Janis Date: Wed, 3 Dec 2025 20:33:20 +0100 Subject: [PATCH 4/8] feat: BAC-27 Implement the API Endpoint on the backend --- .../app/controllers/StatusController.scala | 44 +++---------------- 1 file changed, 7 insertions(+), 37 deletions(-) diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index a52daab..246579d 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -2,14 +2,12 @@ package controllers import auth.{AuthAction, AuthenticatedRequest} import de.knockoutwhist.control.GameState.{FinishedMatch, InGame, Lobby, MainMenu, SelectTrump, TieBreak} -import de.knockoutwhist.player.AbstractPlayer import logic.PodManager import logic.game.GameLobby import logic.user.SessionManager import model.users.User import play.api.libs.json.{JsValue, Json} import play.api.mvc.{Action, *} -import util.WebUIUtils import javax.inject.Inject @@ -97,41 +95,13 @@ class StatusController @Inject()( "players" -> gameLobby.getUsers.map(p => Json.obj("id" -> p.id, "name" -> p.name, "isSelf" -> (p.id == user.id))) ) case SelectTrump => - val findSelector: Option[AbstractPlayer] = gameLobby.logic.getCurrentMatch match { - case Some(matchImpl) => - if (matchImpl.roundlist.isEmpty) None - else { - matchImpl.roundlist.last.winner match { - case Some(winner) => Some(winner) - case None => None - } - } - case None => None - } - - findSelector match { - case Some(selector) => - val isSelf = selector.id == user.id - val playerHand = { - val userPlayer = gameLobby.getPlayerByUser(user) - val handOpt = userPlayer.currentHand() - handOpt match { - case Some(hand) => - WebUIUtils.handToJson(hand) - case None => Json.arr() - } - } - Json.obj( - "selector" -> selector.name, - "isSelf" -> isSelf, - "hand" -> playerHand - ) - case None => Json.obj( - "error" -> "No winner found. Please try again later." - ) - } + Json.obj( + + ) case MainMenu => - Json.obj() + Json.obj( + + ) case InGame => Json.obj( @@ -143,7 +113,7 @@ class StatusController @Inject()( case FinishedMatch => Json.obj( - ) + ) } } -- 2.52.0 From f3ce6ff7ab5e9872b11f86eb96677c5308f2cb61 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 4 Dec 2025 09:21:17 +0100 Subject: [PATCH 5/8] feat: Implemented select trump for BAC-27 --- .../app/controllers/StatusController.scala | 47 +++++++++++++++---- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index 246579d..0672fbb 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -2,12 +2,14 @@ package controllers import auth.{AuthAction, AuthenticatedRequest} import de.knockoutwhist.control.GameState.{FinishedMatch, InGame, Lobby, MainMenu, SelectTrump, TieBreak} +import de.knockoutwhist.player.AbstractPlayer import logic.PodManager import logic.game.GameLobby import logic.user.SessionManager import model.users.User import play.api.libs.json.{JsValue, Json} import play.api.mvc.{Action, *} +import util.WebUIUtils import javax.inject.Inject @@ -95,16 +97,44 @@ class StatusController @Inject()( "players" -> gameLobby.getUsers.map(p => Json.obj("id" -> p.id, "name" -> p.name, "isSelf" -> (p.id == user.id))) ) case SelectTrump => - Json.obj( - - ) + val findSelector: Option[AbstractPlayer] = gameLobby.logic.getCurrentMatch match { + case Some(matchImpl) => + if (matchImpl.roundlist.isEmpty) None + else { + matchImpl.roundlist.last.winner match { + case Some(winner) => Some(winner) + case None => None + } + } + case None => None + } + + findSelector match { + case Some(selector) => + val isSelf = selector.id == user.id + val playerHand = { + val userPlayer = gameLobby.getPlayerByUser(user) + val handOpt = userPlayer.currentHand() + handOpt match { + case Some(hand) => + WebUIUtils.handToJson(hand) + case None => Json.arr() + } + } + Json.obj( + "selector" -> selector.name, + "isSelf" -> isSelf, + "hand" -> playerHand + ) + case None => Json.obj( + "error" -> "No winner found. Please try again later." + ) + } case MainMenu => - Json.obj( - - ) + Json.obj() case InGame => Json.obj( - + ) case TieBreak => Json.obj( @@ -112,8 +142,7 @@ class StatusController @Inject()( ) case FinishedMatch => Json.obj( - - ) + ) } } -- 2.52.0 From da87439a07d82aec74cb56ff1334441d08896698 Mon Sep 17 00:00:00 2001 From: Janis Date: Thu, 4 Dec 2025 09:55:32 +0100 Subject: [PATCH 6/8] feat: Implemented select trump for BAC-27 --- knockoutwhistweb/app/controllers/StatusController.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index 0672fbb..a52daab 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -108,7 +108,7 @@ class StatusController @Inject()( } case None => None } - + findSelector match { case Some(selector) => val isSelf = selector.id == user.id @@ -134,7 +134,7 @@ class StatusController @Inject()( Json.obj() case InGame => Json.obj( - + ) case TieBreak => Json.obj( @@ -142,6 +142,7 @@ class StatusController @Inject()( ) case FinishedMatch => Json.obj( + ) } } -- 2.52.0 From 1914aa67cd7c7409447a311476ae67952d73e861 Mon Sep 17 00:00:00 2001 From: Janis Date: Sat, 6 Dec 2025 10:23:31 +0100 Subject: [PATCH 7/8] feat: BAC-27 Implemented endpoint which returns information about the current state --- knockoutwhistfrontend | 2 +- .../app/controllers/StatusController.scala | 58 +------------------ .../app/util/WebsocketEventMapper.scala | 4 +- 3 files changed, 5 insertions(+), 59 deletions(-) diff --git a/knockoutwhistfrontend b/knockoutwhistfrontend index 5d080bb..a04c370 160000 --- a/knockoutwhistfrontend +++ b/knockoutwhistfrontend @@ -1 +1 @@ -Subproject commit 5d080bba47778d51c8dbbb99d4d7c2b156c5316c +Subproject commit a04c370a7509b95385439b7453fdf8d3c7a304ae diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index a52daab..e2980da 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -9,7 +9,7 @@ import logic.user.SessionManager import model.users.User import play.api.libs.json.{JsValue, Json} import play.api.mvc.{Action, *} -import util.WebUIUtils +import util.{WebUIUtils, WebsocketEventMapper} import javax.inject.Inject @@ -90,61 +90,7 @@ class StatusController @Inject()( private def mapGameState(gameLobby: GameLobby, user: User): JsValue = { val userSession = gameLobby.getUserSession(user.id) - gameLobby.logic.getCurrentState match { - case Lobby => - Json.obj( - "host" -> userSession.host, - "players" -> gameLobby.getUsers.map(p => Json.obj("id" -> p.id, "name" -> p.name, "isSelf" -> (p.id == user.id))) - ) - case SelectTrump => - val findSelector: Option[AbstractPlayer] = gameLobby.logic.getCurrentMatch match { - case Some(matchImpl) => - if (matchImpl.roundlist.isEmpty) None - else { - matchImpl.roundlist.last.winner match { - case Some(winner) => Some(winner) - case None => None - } - } - case None => None - } - - findSelector match { - case Some(selector) => - val isSelf = selector.id == user.id - val playerHand = { - val userPlayer = gameLobby.getPlayerByUser(user) - val handOpt = userPlayer.currentHand() - handOpt match { - case Some(hand) => - WebUIUtils.handToJson(hand) - case None => Json.arr() - } - } - Json.obj( - "selector" -> selector.name, - "isSelf" -> isSelf, - "hand" -> playerHand - ) - case None => Json.obj( - "error" -> "No winner found. Please try again later." - ) - } - case MainMenu => - Json.obj() - case InGame => - Json.obj( - - ) - case TieBreak => - Json.obj( - - ) - case FinishedMatch => - Json.obj( - - ) - } + WebsocketEventMapper.stateToJson(userSession) } } diff --git a/knockoutwhistweb/app/util/WebsocketEventMapper.scala b/knockoutwhistweb/app/util/WebsocketEventMapper.scala index 2a2f011..4e3bb32 100644 --- a/knockoutwhistweb/app/util/WebsocketEventMapper.scala +++ b/knockoutwhistweb/app/util/WebsocketEventMapper.scala @@ -57,12 +57,12 @@ object WebsocketEventMapper { Json.obj( "id" -> ("request-" + java.util.UUID.randomUUID().toString), "event" -> obj.id, - "state" -> toJson(session), + "state" -> stateToJson(session), "data" -> data ) } - def toJson(session: UserSession): JsValue = { + def stateToJson(session: UserSession): JsValue = { session.gameLobby.getLogic.getCurrentState match { case Lobby => Json.toJson(LobbyInfoDTO(session.gameLobby, session.user)) case InGame => Json.toJson(GameInfoDTO(session.gameLobby, session.user)) -- 2.52.0 From 078667fc3f0691677a674305b3a07d67bc65e27d Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 7 Dec 2025 19:12:28 +0100 Subject: [PATCH 8/8] feat: BAC-27 Implemented endpoint which returns information about the current state --- knockoutwhistweb/app/controllers/StatusController.scala | 8 +++----- knockoutwhistweb/conf/routes | 6 +++++- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/knockoutwhistweb/app/controllers/StatusController.scala b/knockoutwhistweb/app/controllers/StatusController.scala index e2980da..72daedb 100644 --- a/knockoutwhistweb/app/controllers/StatusController.scala +++ b/knockoutwhistweb/app/controllers/StatusController.scala @@ -1,15 +1,13 @@ package controllers -import auth.{AuthAction, AuthenticatedRequest} -import de.knockoutwhist.control.GameState.{FinishedMatch, InGame, Lobby, MainMenu, SelectTrump, TieBreak} -import de.knockoutwhist.player.AbstractPlayer +import auth.AuthAction import logic.PodManager import logic.game.GameLobby import logic.user.SessionManager import model.users.User import play.api.libs.json.{JsValue, Json} -import play.api.mvc.{Action, *} -import util.{WebUIUtils, WebsocketEventMapper} +import play.api.mvc.* +import util.WebsocketEventMapper import javax.inject.Inject diff --git a/knockoutwhistweb/conf/routes b/knockoutwhistweb/conf/routes index ac9c094..c5dfd08 100644 --- a/knockoutwhistweb/conf/routes +++ b/knockoutwhistweb/conf/routes @@ -27,4 +27,8 @@ GET /logout controllers.UserController.logout() GET /game/:id controllers.IngameController.game(id: String) # Websocket -GET /websocket controllers.WebsocketController.socket() \ No newline at end of file +GET /websocket controllers.WebsocketController.socket() + +# Status +GET /status controllers.StatusController.requestStatus() +GET /status/:gameId controllers.StatusController.game(gameId: String) \ No newline at end of file -- 2.52.0