Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
410b4829f4 | ||
| 4b17af2c2f | |||
|
|
a163d9f8fe | ||
| dc3da9a75c | |||
|
|
4a4e9c48fc | ||
| 859dfce521 | |||
| 61ae9b5a5e | |||
|
|
98fa5f63d6 | ||
| 0e555cdfeb | |||
| b4bf2ceb4d | |||
|
|
1542906edf | ||
| cf1854976a | |||
|
|
723a4be33f | ||
| 2f89951c25 | |||
|
|
f330c5f3d8 | ||
| 4a5af36ae0 | |||
|
|
edcab594a7 | ||
| 26157076d6 | |||
|
|
103b341488 | ||
| 6ef7401443 |
50
CHANGELOG.md
50
CHANGELOG.md
@@ -287,3 +287,53 @@
|
|||||||
### Features
|
### Features
|
||||||
|
|
||||||
* Add caching headers for env.js in Nginx configuration ([dbad818](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/dbad818fdaeb237a05f583e5402773a4339e7aa1))
|
* Add caching headers for env.js in Nginx configuration ([dbad818](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/dbad818fdaeb237a05f583e5402773a4339e7aa1))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add logging to Gateway for pod synchronization and startup events ([6ef7401](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/6ef74014430673e725245bf37e44c5b90b81abb3))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add logging to Gateway for pod synchronization and startup events ([2615707](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/26157076d686a5dd3f8157ec2b2d1ae9d9e9eedf))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Add Health and Login endpoints with updated Redis configuration ([4a5af36](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/4a5af36ae0dcb540e02b7a1cd042e54cc6342c78))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Update Gateway to use ArrayList for game IDs and bound users ([2f89951](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/2f89951c25484d6bc412536a83019ee6d0b7f780))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Update joinGame endpoint to accept gameId as a path parameter ([cf18549](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/cf1854976a51eb4931d50cf93640498ed18686fc))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Enhance user state management with polling and WebSocket connection handling ([b4bf2ce](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/b4bf2ceb4dc76ac388124b9705a1aa9e577582af))
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* Update knockoutwhistfrontend hash for consistency ([0e555cd](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/0e555cdfeb114464c9438bfd5dc397201a073867))
|
||||||
|
## (2026-01-07)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* Implement PlayDogCard functionality in user session and update Vue component ([859dfce](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/859dfce521b193b9208d0c70fca88016f8fe08f4))
|
||||||
|
* Implement PlayDogCard functionality in user session and update Vue component ([61ae9b5](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/61ae9b5a5e7cd9fd82b77e9159814b0066874c2d))
|
||||||
|
## (2026-01-13)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **ui:** Tie selection ([#111](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/111)) ([dc3da9a](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/dc3da9a75c75597ce81ce4d023af5390197012c9))
|
||||||
|
## (2026-01-14)
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* CORE-4 Rework the delay handler ([#113](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/issues/113)) ([4b17af2](https://git.janis-eccarius.de/KnockOutWhist/KnockOutWhist-Web/commit/4b17af2c2f50a9d67cf1cf49cafdaac8f807d4b6))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
meta {
|
meta {
|
||||||
name: Game
|
name: Game
|
||||||
seq: 3
|
seq: 2
|
||||||
}
|
}
|
||||||
|
|
||||||
auth {
|
auth {
|
||||||
|
|||||||
16
bruno/KnockOutWhist/Health.bru
Normal file
16
bruno/KnockOutWhist/Health.bru
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
meta {
|
||||||
|
name: Health
|
||||||
|
type: http
|
||||||
|
seq: 3
|
||||||
|
}
|
||||||
|
|
||||||
|
get {
|
||||||
|
url: {{host}}/health/simple
|
||||||
|
body: none
|
||||||
|
auth: inherit
|
||||||
|
}
|
||||||
|
|
||||||
|
settings {
|
||||||
|
encodeUrl: true
|
||||||
|
timeout: 0
|
||||||
|
}
|
||||||
3
bruno/KnockOutWhist/environments/Staging.bru
Normal file
3
bruno/KnockOutWhist/environments/Staging.bru
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
vars {
|
||||||
|
host: https://st.knockout.janis-eccarius.de/api
|
||||||
|
}
|
||||||
Submodule knockoutwhist updated: 10fa4badf0...2d6b56ccf1
Submodule knockoutwhistfrontend updated: d8b3904cbc...058d232d2b
30
knockoutwhistweb/app/controllers/HealthController.scala
Normal file
30
knockoutwhistweb/app/controllers/HealthController.scala
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
package controllers
|
||||||
|
|
||||||
|
import auth.{AuthAction, AuthenticatedRequest}
|
||||||
|
import dto.subDTO.UserDTO
|
||||||
|
import logic.user.{SessionManager, UserManager}
|
||||||
|
import model.users.User
|
||||||
|
import play.api.*
|
||||||
|
import play.api.libs.json.Json
|
||||||
|
import play.api.mvc.*
|
||||||
|
import play.api.mvc.Cookie.SameSite.{Lax, None, Strict}
|
||||||
|
|
||||||
|
import javax.inject.*
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This controller creates an `Action` to handle HTTP requests to the
|
||||||
|
* application's home page.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class HealthController @Inject()(
|
||||||
|
val controllerComponents: ControllerComponents,
|
||||||
|
) extends BaseController {
|
||||||
|
|
||||||
|
def simple(): Action[AnyContent] = {
|
||||||
|
Action { implicit request =>
|
||||||
|
Ok("OK")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -55,32 +55,21 @@ class MainMenuController @Inject()(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
def joinGame(): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
def joinGame(gameId: String): Action[AnyContent] = authAction { implicit request: AuthenticatedRequest[AnyContent] =>
|
||||||
val jsonBody = request.body.asJson
|
val game = PodManager.getGame(gameId)
|
||||||
val gameId: Option[String] = jsonBody.flatMap { jsValue =>
|
game match {
|
||||||
(jsValue \ "gameId").asOpt[String]
|
case Some(g) =>
|
||||||
}
|
g.addUser(request.user)
|
||||||
if (gameId.isDefined) {
|
Ok(Json.obj(
|
||||||
val game = PodManager.getGame(gameId.get)
|
"status" -> "success",
|
||||||
game match {
|
"redirectUrl" -> routes.IngameController.game(g.id).url,
|
||||||
case Some(g) =>
|
"content" -> IngameController.returnInnerHTML(g, g.logic.getCurrentState, request.user).toString
|
||||||
g.addUser(request.user)
|
))
|
||||||
Ok(Json.obj(
|
case None =>
|
||||||
"status" -> "success",
|
NotFound(Json.obj(
|
||||||
"redirectUrl" -> routes.IngameController.game(g.id).url,
|
"status" -> "failure",
|
||||||
"content" -> IngameController.returnInnerHTML(g, g.logic.getCurrentState, request.user).toString
|
"errorMessage" -> "No Game found"
|
||||||
))
|
))
|
||||||
case None =>
|
|
||||||
NotFound(Json.obj(
|
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> "No Game found"
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BadRequest(Json.obj(
|
|
||||||
"status" -> "failure",
|
|
||||||
"errorMessage" -> "Invalid form submission"
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class StatusController @Inject()(
|
|||||||
Json.obj(
|
Json.obj(
|
||||||
"status" -> "authenticated",
|
"status" -> "authenticated",
|
||||||
"username" -> user.name,
|
"username" -> user.name,
|
||||||
|
"userId" -> user.id,
|
||||||
"inGame" -> false
|
"inGame" -> false
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -43,6 +44,7 @@ class StatusController @Inject()(
|
|||||||
Json.obj(
|
Json.obj(
|
||||||
"status" -> "authenticated",
|
"status" -> "authenticated",
|
||||||
"username" -> user.name,
|
"username" -> user.name,
|
||||||
|
"userId" -> user.id,
|
||||||
"inGame" -> true,
|
"inGame" -> true,
|
||||||
"gameId" -> game.id
|
"gameId" -> game.id
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import de.knockoutwhist.data.Pod
|
|||||||
import de.knockoutwhist.data.redis.RedisManager
|
import de.knockoutwhist.data.redis.RedisManager
|
||||||
import org.apache.pekko.actor.ActorSystem
|
import org.apache.pekko.actor.ActorSystem
|
||||||
import org.redisson.config.Config
|
import org.redisson.config.Config
|
||||||
|
import play.api.Logger
|
||||||
import play.api.inject.ApplicationLifecycle
|
import play.api.inject.ApplicationLifecycle
|
||||||
|
|
||||||
|
import java.util
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.*
|
import javax.inject.*
|
||||||
import scala.concurrent.ExecutionContext
|
import scala.concurrent.ExecutionContext
|
||||||
@@ -17,15 +19,22 @@ class Gateway @Inject()(
|
|||||||
actorSystem: ActorSystem
|
actorSystem: ActorSystem
|
||||||
)(implicit ec: ExecutionContext) {
|
)(implicit ec: ExecutionContext) {
|
||||||
|
|
||||||
|
private val logger = Logger(getClass.getName)
|
||||||
|
|
||||||
val redis: RedisManager = {
|
val redis: RedisManager = {
|
||||||
val config: Config = Config()
|
val config: Config = Config()
|
||||||
config.useSingleServer.setAddress("valkey://" + sys.env.getOrElse("VALKEY_HOST", "localhost") + ":" + sys.env.getOrElse("VALKEY_PORT", "6379"))
|
val url = "redis://" + sys.env.getOrElse("REDIS_HOST", "localhost") + ":" + sys.env.getOrElse("REDIS_PORT", "6379")
|
||||||
|
logger.info(s"Connecting to Redis at $url")
|
||||||
|
config.useSingleServer.setAddress(url)
|
||||||
RedisManager(config)
|
RedisManager(config)
|
||||||
}
|
}
|
||||||
|
|
||||||
redis.continuousSyncPod(() => {
|
redis.continuousSyncPod(() => {
|
||||||
|
logger.info("Syncing pod with Redis")
|
||||||
createPod()
|
createPod()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
logger.info("Gateway started")
|
||||||
|
|
||||||
def syncPod(): Unit = {
|
def syncPod(): Unit = {
|
||||||
redis.syncPod(createPod())
|
redis.syncPod(createPod())
|
||||||
@@ -37,8 +46,8 @@ class Gateway @Inject()(
|
|||||||
PodManager.podName,
|
PodManager.podName,
|
||||||
PodManager.podIp,
|
PodManager.podIp,
|
||||||
9000,
|
9000,
|
||||||
PodManager.getAllGameIds().asJava,
|
new util.ArrayList[String](PodManager.getAllGameIds().asJava),
|
||||||
PodManager.allBoundUsers().asJava
|
new util.ArrayList[String](PodManager.allBoundUsers().asJava)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import de.knockoutwhist.events.player.PlayerEvent
|
|||||||
import de.knockoutwhist.player.Playertype.HUMAN
|
import de.knockoutwhist.player.Playertype.HUMAN
|
||||||
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
import de.knockoutwhist.player.{AbstractPlayer, PlayerFactory}
|
||||||
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
import de.knockoutwhist.rounds.{Match, Round, Trick}
|
||||||
|
import de.knockoutwhist.utils.DelayHandler
|
||||||
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
import de.knockoutwhist.utils.events.{EventListener, SimpleEvent}
|
||||||
import events.{KickEvent, LeftEvent, LobbyUpdateEvent, UserEvent}
|
import events.{KickEvent, LeftEvent, LobbyUpdateEvent, UserEvent}
|
||||||
import exceptions.*
|
import exceptions.*
|
||||||
@@ -33,6 +34,7 @@ class GameLobby private(
|
|||||||
|
|
||||||
private val users: mutable.Map[UUID, UserSession] = mutable.Map()
|
private val users: mutable.Map[UUID, UserSession] = mutable.Map()
|
||||||
logic.addListener(this)
|
logic.addListener(this)
|
||||||
|
logic.addListener(DelayHandler)
|
||||||
logic.createSession()
|
logic.createSession()
|
||||||
|
|
||||||
def addUser(user: User): UserSession = {
|
def addUser(user: User): UserSession = {
|
||||||
|
|||||||
@@ -54,6 +54,16 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
|
|||||||
case None =>
|
case None =>
|
||||||
println("Card Index not found or is not a number." + data)
|
println("Card Index not found or is not a number." + data)
|
||||||
}
|
}
|
||||||
|
case "PlayDogCard" =>
|
||||||
|
val maybeCardIndex: Option[Int] = (data \ "cardindex").asOpt[Int]
|
||||||
|
maybeCardIndex match {
|
||||||
|
case Some(index) =>
|
||||||
|
val session = gameLobby.getUserSession(user.id)
|
||||||
|
gameLobby.playDogCard(session, index)
|
||||||
|
case None =>
|
||||||
|
val session = gameLobby.getUserSession(user.id)
|
||||||
|
gameLobby.playDogCard(session, -1)
|
||||||
|
}
|
||||||
case "PickTrumpsuit" =>
|
case "PickTrumpsuit" =>
|
||||||
val maybeSuitIndex: Option[Int] = (data \ "suitIndex").asOpt[Int]
|
val maybeSuitIndex: Option[Int] = (data \ "suitIndex").asOpt[Int]
|
||||||
maybeSuitIndex match {
|
maybeSuitIndex match {
|
||||||
@@ -63,6 +73,15 @@ class UserSession(val user: User, val host: Boolean, val gameLobby: GameLobby) e
|
|||||||
case None =>
|
case None =>
|
||||||
println("Card Index not found or is not a number.")
|
println("Card Index not found or is not a number.")
|
||||||
}
|
}
|
||||||
|
case "PickTie" =>
|
||||||
|
val maybeCardIndex: Option[Int] = (data \ "cardIndex").asOpt[Int]
|
||||||
|
maybeCardIndex match {
|
||||||
|
case Some(index) =>
|
||||||
|
val session = gameLobby.getUserSession(user.id)
|
||||||
|
gameLobby.selectTie(session, index)
|
||||||
|
case None =>
|
||||||
|
println("Card Index not found or is not a number.")
|
||||||
|
}
|
||||||
case "KickPlayer" =>
|
case "KickPlayer" =>
|
||||||
val maybePlayerId: Option[String] = (data \ "playerId").asOpt[String]
|
val maybePlayerId: Option[String] = (data \ "playerId").asOpt[String]
|
||||||
maybePlayerId match {
|
maybePlayerId match {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
package modules
|
package modules
|
||||||
|
|
||||||
import com.google.inject.AbstractModule
|
import com.google.inject.AbstractModule
|
||||||
|
import logic.Gateway
|
||||||
|
|
||||||
class GatewayModule extends AbstractModule {
|
class GatewayModule extends AbstractModule {
|
||||||
override def configure(): Unit = {
|
override def configure(): Unit = {
|
||||||
bind(classOf[GatewayModule]).asEagerSingleton()
|
bind(classOf[Gateway]).asEagerSingleton()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ object WebsocketEventMapper {
|
|||||||
|
|
||||||
// Register all custom mappers here
|
// Register all custom mappers here
|
||||||
registerCustomMapper(ReceivedHandEventMapper)
|
registerCustomMapper(ReceivedHandEventMapper)
|
||||||
registerCustomMapper(GameStateEventMapper)
|
//registerCustomMapper(GameStateEventMapper)
|
||||||
registerCustomMapper(CardPlayedEventMapper)
|
registerCustomMapper(CardPlayedEventMapper)
|
||||||
registerCustomMapper(NewRoundEventMapper)
|
registerCustomMapper(NewRoundEventMapper)
|
||||||
registerCustomMapper(NewTrickEventMapper)
|
registerCustomMapper(NewTrickEventMapper)
|
||||||
@@ -54,6 +54,7 @@ object WebsocketEventMapper {
|
|||||||
}else {
|
}else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
//println(s"This is getting sent to client: EVENT: ${obj.id}, STATE: ${session.gameLobby.getLogic.getCurrentState.toString}, STATEDATA: ${stateToJson(session)}, DATA: ${data}")
|
||||||
Json.obj(
|
Json.obj(
|
||||||
"id" -> ("request-" + java.util.UUID.randomUUID().toString),
|
"id" -> ("request-" + java.util.UUID.randomUUID().toString),
|
||||||
"event" -> obj.id,
|
"event" -> obj.id,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ GET /rules controllers.MainMenuController.rules()
|
|||||||
GET /navSPA/:pType controllers.MainMenuController.navSPA(pType)
|
GET /navSPA/:pType controllers.MainMenuController.navSPA(pType)
|
||||||
|
|
||||||
POST /createGame controllers.MainMenuController.createGame()
|
POST /createGame controllers.MainMenuController.createGame()
|
||||||
POST /joinGame controllers.MainMenuController.joinGame()
|
POST /joinGame/:gameId controllers.MainMenuController.joinGame(gameId: String)
|
||||||
|
|
||||||
# User authentication routes
|
# User authentication routes
|
||||||
POST /login controllers.UserController.login_Post()
|
POST /login controllers.UserController.login_Post()
|
||||||
@@ -30,4 +30,7 @@ GET /websocket controllers.WebsocketController.socket()
|
|||||||
|
|
||||||
# Status
|
# Status
|
||||||
GET /status controllers.StatusController.requestStatus()
|
GET /status controllers.StatusController.requestStatus()
|
||||||
GET /status/:gameId controllers.StatusController.game(gameId: String)
|
GET /status/:gameId controllers.StatusController.game(gameId: String)
|
||||||
|
|
||||||
|
# Health
|
||||||
|
GET /health/simple controllers.HealthController.simple()
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
MAJOR=4
|
MAJOR=4
|
||||||
MINOR=20
|
MINOR=29
|
||||||
PATCH=0
|
PATCH=0
|
||||||
|
|||||||
Reference in New Issue
Block a user